diff --git a/src/main/java/com/simibubi/create/foundation/config/CClient.java b/src/main/java/com/simibubi/create/foundation/config/CClient.java index 99229f460..d903b52a1 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CClient.java +++ b/src/main/java/com/simibubi/create/foundation/config/CClient.java @@ -1,5 +1,7 @@ package com.simibubi.create.foundation.config; +import com.simibubi.create.foundation.config.ui.ConfigAnnotations; + public class CClient extends ConfigBase { public ConfigGroup client = group(0, "client", @@ -42,11 +44,11 @@ public class CClient extends ConfigBase { "Offset the overlay from goggle- and hover- information by this many pixels on the Y axis; Use /create overlay"); public ConfigBool overlayCustomColor = b(false, "customColorsOverlay", "Enable this to use your custom colors for the Goggle- and Hover- Overlay"); public ConfigInt overlayBackgroundColor = i(0xf0_100010, Integer.MIN_VALUE, Integer.MAX_VALUE, "customBackgroundOverlay", - "The custom background color to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", "[@cui:IntDisplay:#]"); + "The custom background color to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", ConfigAnnotations.IntDisplay.HEX.asComment()); public ConfigInt overlayBorderColorTop = i(0x50_5000ff, Integer.MIN_VALUE, Integer.MAX_VALUE, "customBorderTopOverlay", - "The custom top color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", "[@cui:IntDisplay:#]"); + "The custom top color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", ConfigAnnotations.IntDisplay.HEX.asComment()); public ConfigInt overlayBorderColorBot = i(0x50_28007f, Integer.MIN_VALUE, Integer.MAX_VALUE, "customBorderBotOverlay", - "The custom bot color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", "[@cui:IntDisplay:#]"); + "The custom bot color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled", "[in Hex: #AaRrGgBb]", ConfigAnnotations.IntDisplay.HEX.asComment()); //placement assist group public ConfigGroup placementAssist = group(1, "placementAssist", "Settings for the Placement Assist"); diff --git a/src/main/java/com/simibubi/create/foundation/config/CKinetics.java b/src/main/java/com/simibubi/create/foundation/config/CKinetics.java index b5fe5002d..8739dd8e0 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CKinetics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CKinetics.java @@ -1,11 +1,13 @@ package com.simibubi.create.foundation.config; +import com.simibubi.create.foundation.config.ui.ConfigAnnotations; + public class CKinetics extends ConfigBase { public ConfigBool disableStress = b(false, "disableStress", Comments.disableStress); public ConfigInt maxBeltLength = i(20, 5, "maxBeltLength", Comments.maxBeltLength); public ConfigInt crushingDamage = i(4, 0, "crushingDamage", Comments.crushingDamage); - public ConfigInt maxMotorSpeed = i(256, 64, "maxMotorSpeed", Comments.rpm, Comments.maxMotorSpeed); + public ConfigInt maxMotorSpeed = i(256, 64, "maxMotorSpeed", Comments.rpm, Comments.maxMotorSpeed, ConfigAnnotations.RequiresRestart.BOTH.asComment()); public ConfigInt waterWheelBaseSpeed = i(4, 1, "waterWheelBaseSpeed", Comments.rpm, Comments.waterWheelBaseSpeed); public ConfigInt waterWheelFlowSpeed = i(4, 1, "waterWheelFlowSpeed", Comments.rpm, Comments.waterWheelFlowSpeed); public ConfigInt furnaceEngineSpeed = i(16, 1, "furnaceEngineSpeed", Comments.rpm, Comments.furnaceEngineSpeed); diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java index 1df945e6a..a22ddc419 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java @@ -1,6 +1,9 @@ package com.simibubi.create.foundation.config.ui; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,43 +29,65 @@ import net.minecraftforge.fml.config.ModConfig; public class BaseConfigScreen extends ConfigScreen { - private static final DelegatedStencilElement.ElementRenderer DISABLED_RENDERER = (ms, width, height, alpha) -> UIRenderHelper.angledGradient(ms, 0, 0, height / 2, height, width, Theme.p(Theme.Key.BUTTON_DISABLE)); + public static final DelegatedStencilElement.ElementRenderer DISABLED_RENDERER = (ms, width, height, alpha) -> UIRenderHelper.angledGradient(ms, 0, 0, height / 2, height, width, Theme.p(Theme.Key.BUTTON_DISABLE)); + private static final Map> defaults = new HashMap<>(); + + static { + defaults.put("create", (base) -> base + .withTitles("Client Settings", "World Generation Settings", "Gameplay Settings") + .withSpecs(AllConfigs.CLIENT.specification, AllConfigs.COMMON.specification, AllConfigs.SERVER.specification) + ); + } + + /** + * If you are a Create Addon dev and want to change the config labels, + * add a default action here. + * + * Make sure you call either {@link #withSpecs(ForgeConfigSpec, ForgeConfigSpec, ForgeConfigSpec)} + * or {@link #searchForSpecsInModContainer()} + * + * @param modID the modID of your addon/mod + */ + public static void setDefaultActionFor(String modID, UnaryOperator transform) { + if (modID.equalsIgnoreCase("create")) + return; + + defaults.put(modID, transform); + } public static BaseConfigScreen forCreate(Screen parent) { - return new BaseConfigScreen(parent) - .withTitles("Client Settings", "World Generation Settings", "Gameplay Settings") - .withSpecs(AllConfigs.CLIENT.specification, AllConfigs.COMMON.specification, AllConfigs.SERVER.specification); + return new BaseConfigScreen(parent); } BoxWidget clientConfigWidget; BoxWidget commonConfigWidget; BoxWidget serverConfigWidget; BoxWidget goBack; + BoxWidget others; BoxWidget title; ForgeConfigSpec clientSpec; ForgeConfigSpec commonSpec; ForgeConfigSpec serverSpec; - String clientTile = "CLIENT CONFIG"; - String commonTile = "COMMON CONFIG"; - String serverTile = "SERVER CONFIG"; - String modID = Create.ID; + String clientTile = "Client Config"; + String commonTile = "Common Config"; + String serverTile = "Server Config"; + String modID; protected boolean returnOnClose; - /** - * If you are a Create Addon dev and want to make use of the same GUI - * for your mod's config, use this Constructor to create a entry point - * - * @param parent the previously opened screen - * @param modID the modID of your addon/mod - */ public BaseConfigScreen(Screen parent, @Nonnull String modID) { - this(parent); + super(parent); this.modID = modID; + + if (defaults.containsKey(modID)) + defaults.get(modID).apply(this); + else { + this.searchForSpecsInModContainer(); + } } private BaseConfigScreen(Screen parent) { - super(parent); + this(parent, Create.ID); } /** @@ -70,22 +95,26 @@ public class BaseConfigScreen extends ConfigScreen { * please use {@link #withSpecs(ForgeConfigSpec, ForgeConfigSpec, ForgeConfigSpec)} instead */ public BaseConfigScreen searchForSpecsInModContainer() { + if (!ConfigHelper.hasAnyConfig(this.modID)){ + return this; + } + try { clientSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.CLIENT, this.modID); } catch (Exception e) { - Create.LOGGER.warn("Unable to find ClientConfigSpec for mod: " + this.modID); + Create.LOGGER.debug("Unable to find ClientConfigSpec for mod: " + this.modID); } try { commonSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.COMMON, this.modID); } catch (Exception e) { - Create.LOGGER.warn("Unable to find CommonConfigSpec for mod: " + this.modID, e); + Create.LOGGER.debug("Unable to find CommonConfigSpec for mod: " + this.modID); } try { serverSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.SERVER, this.modID); } catch (Exception e) { - Create.LOGGER.warn("Unable to find ServerConfigSpec for mod: " + this.modID, e); + Create.LOGGER.debug("Unable to find ServerConfigSpec for mod: " + this.modID); } return this; @@ -191,6 +220,13 @@ public class BaseConfigScreen extends ConfigScreen { goBack.getToolTip() .add(new StringTextComponent("Go Back")); widgets.add(goBack); + + TextStencilElement othersText = new TextStencilElement(minecraft.font, new StringTextComponent("Access Configs of other Mods")).centered(true, true); + others = new BoxWidget(width / 2 - 100, height / 2 - 15 + 90, 200, 16).showingElement(othersText); + othersText.withElementRenderer(BoxWidget.gradientFactory.apply(others)); + others.withCallback(() -> linkTo(new ConfigModListScreen(this))); + widgets.add(others); + } @Override diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigAnnotations.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigAnnotations.java new file mode 100644 index 000000000..f52290fae --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigAnnotations.java @@ -0,0 +1,112 @@ +package com.simibubi.create.foundation.config.ui; + +public class ConfigAnnotations { + + + /** + * Changes the way the Integer value is display. + */ + public enum IntDisplay implements ConfigAnnotation { + HEX("#"), + ZERO_X("0x"), + ZERO_B("0b"); + + private final String value; + + IntDisplay(String value) { + this.value = value; + } + + @Override + public String getName() { + return "IntDisplay"; + } + + @Override + public String getValue() { + return value; + } + } + + /** + * Indicates to the player that changing this value will require a restart to take full effect + */ + public enum RequiresRestart implements ConfigAnnotation { + CLIENT("client"), + SERVER("server"), + BOTH("both"); + + private final String value; + + RequiresRestart(String value) { + this.value = value; + } + + @Override + public String getName() { + return "RequiresReload"; + } + + @Override + public String getValue() { + return value; + } + } + + /** + * Indicates to the player that changing this value will require them to relog to take full effect + */ + public enum RequiresRelog implements ConfigAnnotation { + TRUE; + + @Override + public String getName() { + return "RequiresRelog"; + } + } + + /** + * Changing a value that is annotated with Execute will cause the player to run the given command automatically. + */ + public static class Execute implements ConfigAnnotation { + + private final String command; + + public static Execute run(String command) { + return new Execute(command); + } + + private Execute(String command) { + this.command = command; + } + + @Override + public String getName() { + return "Execute"; + } + + @Override + public String getValue() { + return command; + } + } + + public interface ConfigAnnotation { + String getName(); + + default String getValue() { + return null; + } + + default String asComment() { + String comment = "[@cui:" + getName(); + String value = getValue(); + if (value != null) { + comment = comment + ":" + value; + } + comment = comment + "]"; + return comment; + } + + } +} diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java index fb3f4212b..5dbb2e542 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java @@ -2,18 +2,25 @@ package com.simibubi.create.foundation.config.ui; import java.util.Arrays; import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.simibubi.create.Create; import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.utility.Pair; import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.ModContainer; @@ -23,6 +30,10 @@ import net.minecraftforge.fml.config.ModConfig; public class ConfigHelper { + public static final Pattern unitPattern = Pattern.compile("\\[(in .*)]"); + public static final Pattern annotationPattern = Pattern.compile("\\[@cui:([^:]*)(?::(.*))?]"); + + public static final Map changes = new HashMap<>(); private static final LoadingCache> configCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build( new CacheLoader>() { @Override @@ -54,6 +65,11 @@ public class ConfigHelper { return null; } + public static boolean hasAnyConfig(String modID) { + EnumMap map = configCache.getUnchecked(modID); + return map.entrySet().size() > 0; + } + //Directly set a value public static void setConfigValue(ConfigPath path, String value) throws InvalidValueException { ForgeConfigSpec spec = findConfigSpecFor(path.getType(), path.getModID()); @@ -68,18 +84,51 @@ public class ConfigHelper { } //Add a value to the current UI's changes list - public static void setValue(String path, ForgeConfigSpec.ConfigValue configValue, T value) { + public static void setValue(String path, ForgeConfigSpec.ConfigValue configValue, T value, @Nullable Map annotations) { if (value.equals(configValue.get())) { - ConfigScreen.changes.remove(path); + changes.remove(path); } else { - ConfigScreen.changes.put(path, value); + changes.put(path, annotations == null ? new ConfigChange(value) : new ConfigChange(value, annotations)); } } //Get a value from the current UI's changes list or the config value, if its unchanged public static T getValue(String path, ForgeConfigSpec.ConfigValue configValue) { - //noinspection unchecked - return (T) ConfigScreen.changes.getOrDefault(path, configValue.get()); + ConfigChange configChange = changes.get(path); + if (configChange != null) + //noinspection unchecked + return (T) configChange.value; + else + return configValue.get(); + } + + public static Pair> readMetadataFromComment(List commentLines) { + AtomicReference unit = new AtomicReference<>(); + Map annotations = new HashMap<>(); + + commentLines.removeIf(line -> { + if (line.trim().isEmpty()) { + return true; + } + + Matcher matcher = annotationPattern.matcher(line); + if (matcher.matches()) { + String annotation = matcher.group(1); + String aValue = matcher.group(2); + annotations.putIfAbsent(annotation, aValue); + + return true; + } + + matcher = unitPattern.matcher(line); + if (matcher.matches()) { + unit.set(matcher.group(1)); + } + + return false; + }); + + return Pair.of(unit.get(), annotations); } public static class ConfigPath { @@ -138,5 +187,20 @@ public class ConfigHelper { } } + public static class ConfigChange { + Object value; + Map annotations; + + ConfigChange(Object value) { + this.value = value; + } + + ConfigChange(Object value, Map annotations) { + this(value); + this.annotations = new HashMap<>(); + this.annotations.putAll(annotations); + } + } + public static class InvalidValueException extends Exception {} } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigModListScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigModListScreen.java new file mode 100644 index 000000000..c335618c9 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigModListScreen.java @@ -0,0 +1,156 @@ +package com.simibubi.create.foundation.config.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.gui.DelegatedStencilElement; +import com.simibubi.create.foundation.gui.ScreenOpener; +import com.simibubi.create.foundation.gui.Theme; +import com.simibubi.create.foundation.gui.widgets.BoxWidget; +import com.simibubi.create.foundation.item.TooltipHelper; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; + +public class ConfigModListScreen extends ConfigScreen { + + ConfigScreenList list; + HintableTextFieldWidget search; + BoxWidget goBack; + List allEntries; + + public ConfigModListScreen(Screen parent) { + super(parent); + } + + @Override + public void tick() { + super.tick(); + list.tick(); + } + + @Override + protected void init() { + widgets.clear(); + super.init(); + + int listWidth = Math.min(width - 80, 300); + + list = new ConfigScreenList(minecraft, listWidth, height - 60, 15, height - 45, 40); + list.setLeftPos(this.width / 2 - list.getWidth() / 2); + children.add(list); + + allEntries = new ArrayList<>(); + ModList.get().getMods().stream().map(ModInfo::getModId).forEach(id -> allEntries.add(new ModEntry(id, this))); + allEntries.sort((e1, e2) -> { + int empty = (e2.button.active ? 1 : 0) - (e1.button.active ? 1 : 0); + if (empty != 0) + return empty; + + return e1.id.compareToIgnoreCase(e2.id); + }); + list.children().clear(); + list.children().addAll(allEntries); + + goBack = new BoxWidget(width / 2 - listWidth / 2 - 30, height / 2 + 65, 20, 20).withPadding(2, 2) + .withCallback(this::onClose); + goBack.showingElement(AllIcons.I_CONFIG_BACK.asStencil() + .withElementRenderer(BoxWidget.gradientFactory.apply(goBack))); + goBack.getToolTip() + .add(new StringTextComponent("Go Back")); + widgets.add(goBack); + + search = new HintableTextFieldWidget(font, width / 2 - listWidth / 2, height - 35, listWidth, 20); + search.setResponder(this::updateFilter); + search.setHint("Search.."); + search.moveCursorToStart(); + widgets.add(search); + + } + + @Override + protected void renderWindow(MatrixStack ms, int mouseX, int mouseY, float partialTicks) { + + list.render(ms, mouseX, mouseY, partialTicks); + + } + + @Override + public void onClose() { + super.onClose(); + ScreenOpener.open(parent); + } + + private void updateFilter(String search) { + list.children().clear(); + allEntries + .stream() + .filter(modEntry -> modEntry.id.contains(search.toLowerCase(Locale.ROOT))) + .forEach(list.children()::add); + + list.setScrollAmount(list.getScrollAmount()); + if (list.children().size() > 0) { + this.search.setTextColor(Theme.i(Theme.Key.TEXT)); + } else { + this.search.setTextColor(Theme.i(Theme.Key.BUTTON_FAIL)); + } + } + + public static class ModEntry extends ConfigScreenList.LabeledEntry { + + protected BoxWidget button; + protected String id; + + public ModEntry(String id, Screen parent) { + super(toHumanReadable(id)); + this.id = id; + + button = new BoxWidget(0, 0, 35, 16) + .showingElement(AllIcons.I_CONFIG_OPEN.asStencil().at(10, 0)); + button.modifyElement(e -> ((DelegatedStencilElement) e).withElementRenderer(BoxWidget.gradientFactory.apply(button))); + + if (ConfigHelper.hasAnyConfig(id)) { + button.withCallback(() -> ScreenOpener.open(new BaseConfigScreen(parent, id))); + } else { + button.active = false; + button.updateColorsFromState(); + button.modifyElement(e -> ((DelegatedStencilElement) e).withElementRenderer(BaseConfigScreen.DISABLED_RENDERER)); + labelTooltip.add(new StringTextComponent(toHumanReadable(id))); + labelTooltip.addAll(TooltipHelper.cutTextComponent(new StringTextComponent("This Mod does not have any configs registered or is not using Forge's config system"), TextFormatting.GRAY, TextFormatting.GRAY)); + } + + listeners.add(button); + } + + public String getId() { + return id; + } + + @Override + public void tick() { + super.tick(); + button.tick(); + } + + @Override + public void render(MatrixStack ms, int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean p_230432_9_, float partialTicks) { + super.render(ms, index, y, x, width, height, mouseX, mouseY, p_230432_9_, partialTicks); + + button.x = x + width - 108; + button.y = y + 10; + button.setHeight(height - 20); + button.render(ms, mouseX, mouseY, partialTicks); + } + + @Override + protected int getLabelWidth(int totalWidth) { + return (int) (totalWidth * labelWidthMult) + 30; + } + } +} diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java index 797c4758f..194ea921f 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java @@ -39,11 +39,9 @@ public abstract class ConfigScreen extends AbstractSimiScreen { /* * - * zelo's list for configUI + * TO DO * * reduce number of packets sent to the server when saving a bunch of values - * maybe replace java's awt color with something mutable - * find out why framebuffer blending is incorrect * * FIXME * @@ -54,7 +52,6 @@ public abstract class ConfigScreen extends AbstractSimiScreen { public static final Map> backgrounds = new HashMap<>(); public static final PhysicalFloat cogSpin = PhysicalFloat.create().withLimit(10f).withDrag(0.3).addForce(new Force.Static(.2f)); public static final BlockState cogwheelState = AllBlocks.LARGE_COGWHEEL.getDefaultState().setValue(CogWheelBlock.AXIS, Direction.Axis.Y); - public static final Map changes = new HashMap<>(); public static String modID = null; protected final Screen parent; @@ -147,7 +144,7 @@ public abstract class ConfigScreen extends AbstractSimiScreen { public static String toHumanReadable(String key) { String s = key.replaceAll("_", " "); s = Arrays.stream(StringUtils.splitByCharacterTypeCamelCase(s)).map(StringUtils::capitalize).collect(Collectors.joining(" ")); - s = s.replaceAll("\\s\\s+", " "); + s = StringUtils.normalizeSpace(s); return s; } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreenList.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreenList.java index 9c4bedf6c..796a8086b 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreenList.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreenList.java @@ -1,7 +1,11 @@ package com.simibubi.create.foundation.config.ui; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; import org.lwjgl.opengl.GL11; @@ -12,6 +16,7 @@ import com.simibubi.create.foundation.gui.TextStencilElement; import com.simibubi.create.foundation.gui.Theme; import com.simibubi.create.foundation.gui.UIRenderHelper; import com.simibubi.create.foundation.utility.Color; +import com.simibubi.create.foundation.utility.animation.LerpedFloat; import net.minecraft.client.MainWindow; import net.minecraft.client.Minecraft; @@ -28,8 +33,6 @@ public class ConfigScreenList extends ExtendedList { public static TextFieldWidget currentText; - public boolean isForServer = false; - public ConfigScreenList(Minecraft client, int width, int height, int top, int bottom, int elementHeight) { super(client, width, height, top, bottom, elementHeight); setRenderBackground(false); @@ -77,13 +80,41 @@ public class ConfigScreenList extends ExtendedList { } public void tick() { - for(int i = 0; i < getItemCount(); ++i) { + /*for(int i = 0; i < getItemCount(); ++i) { int top = this.getRowTop(i); int bot = top + itemHeight; if (bot >= this.y0 && top <= this.y1) this.getEntry(i).tick(); + }*/ + children().forEach(Entry::tick); + + } + + public boolean search(String query) { + if (query == null || query.isEmpty()) { + setScrollAmount(0); + return true; } + String q = query.toLowerCase(Locale.ROOT); + Optional first = children().stream().filter(entry -> { + if (entry.path == null) + return false; + + String[] split = entry.path.split("\\."); + String key = split[split.length - 1].toLowerCase(Locale.ROOT); + return key.contains(q); + }).findFirst(); + + if (!first.isPresent()) { + setScrollAmount(0); + return false; + } + + Entry e = first.get(); + e.annotations.put("highlight", "(:"); + centerScrollOn(e); + return true; } public void bumpCog(float force) { @@ -92,9 +123,12 @@ public class ConfigScreenList extends ExtendedList { public static abstract class Entry extends ExtendedList.AbstractListEntry { protected List listeners; + protected Map annotations; + protected String path; protected Entry() { listeners = new ArrayList<>(); + annotations = new HashMap<>(); } @Override @@ -119,6 +153,13 @@ public class ConfigScreenList extends ExtendedList { } protected void setEditable(boolean b) {} + + protected boolean isCurrentValueChanged() { + if (path == null) { + return false; + } + return ConfigHelper.changes.containsKey(path); + } } public static class LabeledEntry extends Entry { @@ -128,6 +169,8 @@ public class ConfigScreenList extends ExtendedList { protected TextStencilElement label; protected List labelTooltip; protected String unit = null; + protected LerpedFloat differenceAnimation = LerpedFloat.linear().startWithValue(0); + protected LerpedFloat highlightAnimation = LerpedFloat.linear().startWithValue(0); public LabeledEntry(String label) { this.label = new TextStencilElement(Minecraft.getInstance().font, label); @@ -135,8 +178,41 @@ public class ConfigScreenList extends ExtendedList { labelTooltip = new ArrayList<>(); } + public LabeledEntry(String label, String path) { + this(label); + this.path = path; + } + + @Override + public void tick() { + differenceAnimation.tickChaser(); + highlightAnimation.tickChaser(); + super.tick(); + } + @Override public void render(MatrixStack ms, int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean p_230432_9_, float partialTicks) { + if (isCurrentValueChanged()) { + if (differenceAnimation.getChaseTarget() != 1) + differenceAnimation.chase(1, .5f, LerpedFloat.Chaser.EXP); + } else { + if (differenceAnimation.getChaseTarget() != 0) + differenceAnimation.chase(0, .6f, LerpedFloat.Chaser.EXP); + } + + float animation = differenceAnimation.getValue(partialTicks); + if (animation > .1f) { + int offset = (int) (30 * (1 - animation)); + + if (annotations.containsKey(ConfigAnnotations.RequiresRestart.CLIENT.getName())) { + UIRenderHelper.streak(ms, 180, x + width + 10 + offset, y + height / 2, height - 6, 110, new Color(0x50_601010)); + } else if (annotations.containsKey(ConfigAnnotations.RequiresRelog.TRUE.getName())) { + UIRenderHelper.streak(ms, 180, x + width + 10 + offset, y + height / 2, height - 6, 110, new Color(0x40_eefb17)); + } + + UIRenderHelper.breadcrumbArrow(ms, x - 10 - offset, y + 6, 0, -20, 24, -18, new Color(0x70_ffffff), Color.TRANSPARENT_BLACK); + } + UIRenderHelper.streak(ms, 0, x - 10, y + height / 2, height - 6, width / 8 * 7, 0xdd_000000); UIRenderHelper.streak(ms, 180, x + (int) (width * 1.35f) + 10, y + height / 2, height - 6, width / 8 * 7, 0xdd_000000); IFormattableTextComponent component = label.getComponent(); @@ -152,6 +228,20 @@ public class ConfigScreenList extends ExtendedList { label.at(x + 10, y + height / 2 - 4, 0).render(ms); } + if (annotations.containsKey("highlight")) { + highlightAnimation.startWithValue(1).chase(0, 0.1f, LerpedFloat.Chaser.LINEAR); + annotations.remove("highlight"); + } + + animation = highlightAnimation.getValue(partialTicks); + if (animation > .01f) { + Color highlight = new Color(0xa0_ffffff).scaleAlpha(animation); + UIRenderHelper.streak(ms, 0, x - 10, y + height / 2, height - 6, 5, highlight); + UIRenderHelper.streak(ms, 180, x + width, y + height / 2, height - 6, 5, highlight); + UIRenderHelper.streak(ms, 90, x + width / 2 - 5, y + 3, width + 10, 5, highlight); + UIRenderHelper.streak(ms, -90, x + width / 2 - 5, y + height - 3, width + 10, 5, highlight); + } + if (mouseX > x && mouseX < x + getLabelWidth(width) && mouseY > y + 5 && mouseY < y + height - 5) { List tooltip = getLabelTooltip(); diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigTextField.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigTextField.java index 4eb851afd..f8e371ca7 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigTextField.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigTextField.java @@ -1,20 +1,11 @@ package com.simibubi.create.foundation.config.ui; -import org.lwjgl.glfw.GLFW; - import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.util.text.StringTextComponent; -public class ConfigTextField extends TextFieldWidget { +public class ConfigTextField extends HintableTextFieldWidget { - protected FontRenderer font; - protected String unit; - - public ConfigTextField(FontRenderer font, int x, int y, int width, int height, String unit) { - super(font, x, y, width, height, StringTextComponent.EMPTY); - this.font = font; - this.unit = unit; + public ConfigTextField(FontRenderer font, int x, int y, int width, int height) { + super(font, x, y, width, height); } @Override @@ -33,29 +24,4 @@ public class ConfigTextField extends TextFieldWidget { ConfigScreenList.currentText = this; } - - @Override - public boolean keyPressed(int p_231046_1_, int p_231046_2_, int p_231046_3_) { - //prevent input of hex-keys from exiting the current config screen, in case they match the inventory key - boolean ret = false; - - switch (p_231046_1_) { - case GLFW.GLFW_KEY_A: - case GLFW.GLFW_KEY_B: - case GLFW.GLFW_KEY_C: - case GLFW.GLFW_KEY_D: - case GLFW.GLFW_KEY_E: - case GLFW.GLFW_KEY_F: - ret = true; - break; - default: - break; - } - - if (ret) - return true; - - - return super.keyPressed(p_231046_1_, p_231046_2_, p_231046_3_); - } } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/HintableTextFieldWidget.java b/src/main/java/com/simibubi/create/foundation/config/ui/HintableTextFieldWidget.java new file mode 100644 index 000000000..3be40c309 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/config/ui/HintableTextFieldWidget.java @@ -0,0 +1,62 @@ +package com.simibubi.create.foundation.config.ui; + +import org.lwjgl.glfw.GLFW; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.gui.Theme; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.util.InputMappings; +import net.minecraft.util.text.StringTextComponent; + +public class HintableTextFieldWidget extends TextFieldWidget { + + protected FontRenderer font; + protected String hint; + + public HintableTextFieldWidget(FontRenderer font, int x, int y, int width, int height) { + super(font, x, y, width, height, StringTextComponent.EMPTY); + this.font = font; + } + + public void setHint(String hint) { + this.hint = hint; + } + + @Override + public void renderButton(MatrixStack ms, int mouseX, int mouseY, float partialTicks) { + super.renderButton(ms, mouseX, mouseY, partialTicks); + + if (hint == null || hint.isEmpty()) + return; + + if (!getValue().isEmpty()) + return; + + font.draw(ms, hint, x + 5, this.y + (this.height - 8) / 2, Theme.c(Theme.Key.TEXT).scaleAlpha(.75f).getRGB()); + } + + @Override + public boolean mouseClicked(double x, double y, int button) { + if (!isMouseOver(x, y)) + return false; + + if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + setValue(""); + return true; + } else + return super.mouseClicked(x, y, button); + } + + @Override + public boolean keyPressed(int code, int p_keyPressed_2_, int p_keyPressed_3_) { + InputMappings.Input mouseKey = InputMappings.getKey(code, p_keyPressed_2_); + if (Minecraft.getInstance().options.keyInventory.isActiveAndMatches(mouseKey)) { + return true; + } + + return super.keyPressed(code, p_keyPressed_2_, p_keyPressed_3_); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java index e19540a2e..71152af60 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java @@ -1,9 +1,14 @@ package com.simibubi.create.foundation.config.ui; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -33,6 +38,7 @@ import com.simibubi.create.foundation.item.TooltipHelper; import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Couple; +import com.simibubi.create.foundation.utility.Pair; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.IGuiEventListener; @@ -55,13 +61,15 @@ public class SubMenuConfigScreen extends ConfigScreen { protected BoxWidget discardChanges; protected BoxWidget goBack; protected BoxWidget serverLocked; + protected HintableTextFieldWidget search; protected int listWidth; protected String title; + protected Set highlights = new HashSet<>(); public static SubMenuConfigScreen find(ConfigHelper.ConfigPath path) { ForgeConfigSpec spec = ConfigHelper.findConfigSpecFor(path.getType(), path.getModID()); UnmodifiableConfig values = spec.getValues(); - BaseConfigScreen base = new BaseConfigScreen(null, path.getModID()).searchForSpecsInModContainer(); + BaseConfigScreen base = new BaseConfigScreen(null, path.getModID()); SubMenuConfigScreen screen = new SubMenuConfigScreen(base, "root", path.getType(), spec, values); List remainingPath = Lists.newArrayList(path.getPath()); @@ -75,6 +83,7 @@ public class SubMenuConfigScreen extends ConfigScreen { if (!(obj instanceof AbstractConfig)) { //highlight entry + screen.highlights.add(path.getPath()[path.getPath().length - 1]); continue; } @@ -107,7 +116,7 @@ public class SubMenuConfigScreen extends ConfigScreen { } protected void clearChanges() { - changes.clear(); + ConfigHelper.changes.clear(); list.children() .stream() .filter(e -> e instanceof ValueEntry) @@ -116,11 +125,18 @@ public class SubMenuConfigScreen extends ConfigScreen { protected void saveChanges() { UnmodifiableConfig values = spec.getValues(); - changes.forEach((path, value) -> { + ConfigHelper.changes.forEach((path, change) -> { ForgeConfigSpec.ConfigValue configValue = values.get(path); - configValue.set(value); + configValue.set(change.value); + if (type == ModConfig.Type.SERVER) { - AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(ConfigScreen.modID, path, value)); + AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(ConfigScreen.modID, path, change.value)); + } + + String command = change.annotations.get("Execute"); + if (Minecraft.getInstance().player != null && command != null && command.startsWith("/")) { + Minecraft.getInstance().player.chat(command); + //AllPackets.channel.sendToServer(new CChatMessagePacket(command)); } }); clearChanges(); @@ -133,8 +149,10 @@ public class SubMenuConfigScreen extends ConfigScreen { } else if (obj instanceof ForgeConfigSpec.ConfigValue) { ForgeConfigSpec.ConfigValue configValue = (ForgeConfigSpec.ConfigValue) obj; ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw((List) configValue.getPath()); + List comments = new ArrayList<>(Arrays.asList(valueSpec.getComment().split("\n"))); + Pair> metadata = ConfigHelper.readMetadataFromComment(comments); - ConfigHelper.setValue(String.join(".", configValue.getPath()), configValue, valueSpec.getDefault()); + ConfigHelper.setValue(String.join(".", configValue.getPath()), configValue, valueSpec.getDefault(), metadata.getSecond()); } }); @@ -181,17 +199,18 @@ public class SubMenuConfigScreen extends ConfigScreen { saveChanges = new BoxWidget(listL - 30, yCenter - 25, 20, 20) .withPadding(2, 2) .withCallback((x, y) -> { - if (changes.isEmpty()) + if (ConfigHelper.changes.isEmpty()) return; - new ConfirmationScreen() + ConfirmationScreen confirm = new ConfirmationScreen() .centered() - .withText(ITextProperties.of("Saving " + changes.size() + " changed value" + (changes.size() != 1 ? "s" : "") + "")) + .withText(ITextProperties.of("Saving " + ConfigHelper.changes.size() + " changed value" + (ConfigHelper.changes.size() != 1 ? "s" : "") + "")) .withAction(success -> { if (success) saveChanges(); - }) - .open(this); + }); + + addAnnotationsToConfirm(confirm).open(this); }); saveChanges.showingElement(AllIcons.I_CONFIG_SAVE.asStencil().withElementRenderer(BoxWidget.gradientFactory.apply(saveChanges))); saveChanges.getToolTip().add(new StringTextComponent("Save Changes")); @@ -200,12 +219,12 @@ public class SubMenuConfigScreen extends ConfigScreen { discardChanges = new BoxWidget(listL - 30, yCenter + 5, 20, 20) .withPadding(2, 2) .withCallback((x, y) -> { - if (changes.isEmpty()) + if (ConfigHelper.changes.isEmpty()) return; new ConfirmationScreen() .centered() - .withText(ITextProperties.of("Discarding " + changes.size() + " unsaved change" + (changes.size() != 1 ? "s" : "") + "")) + .withText(ITextProperties.of("Discarding " + ConfigHelper.changes.size() + " unsaved change" + (ConfigHelper.changes.size() != 1 ? "s" : "") + "")) .withAction(success -> { if (success) clearChanges(); @@ -227,16 +246,23 @@ public class SubMenuConfigScreen extends ConfigScreen { widgets.add(discardChanges); widgets.add(goBack); - list = new ConfigScreenList(minecraft, listWidth, height - 60, 45, height - 15, 40); + list = new ConfigScreenList(minecraft, listWidth, height - 80, 35, height - 45, 40); list.setLeftPos(this.width / 2 - list.getWidth() / 2); children.add(list); + search = new ConfigTextField(font, width / 2 - listWidth / 2, height - 35, listWidth, 20); + search.setResponder(this::updateFilter); + search.setHint("Search.."); + search.moveCursorToStart(); + widgets.add(search); + configGroup.valueMap().forEach((key, obj) -> { String humanKey = toHumanReadable(key); if (obj instanceof AbstractConfig) { SubMenuEntry entry = new SubMenuEntry(this, humanKey, spec, (UnmodifiableConfig) obj); + entry.path = key; list.children().add(entry); if (configGroup.valueMap() .size() == 1) @@ -247,23 +273,23 @@ public class SubMenuConfigScreen extends ConfigScreen { ForgeConfigSpec.ConfigValue configValue = (ForgeConfigSpec.ConfigValue) obj; ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(configValue.getPath()); Object value = configValue.get(); + ConfigScreenList.Entry entry = null; if (value instanceof Boolean) { - BooleanEntry entry = new BooleanEntry(humanKey, (ForgeConfigSpec.ConfigValue) configValue, valueSpec); - list.children().add(entry); + entry = new BooleanEntry(humanKey, (ForgeConfigSpec.ConfigValue) configValue, valueSpec); } else if (value instanceof Enum) { - EnumEntry entry = new EnumEntry(humanKey, (ForgeConfigSpec.ConfigValue>) configValue, valueSpec); - list.children().add(entry); + entry = new EnumEntry(humanKey, (ForgeConfigSpec.ConfigValue>) configValue, valueSpec); } else if (value instanceof Number) { - NumberEntry entry = NumberEntry.create(value, humanKey, configValue, valueSpec); - if (entry != null) { - list.children().add(entry); - } else { - list.children().add(new ConfigScreenList.LabeledEntry("n-" + obj.getClass().getSimpleName() + " " + humanKey + " : " + value)); - } - } else { - list.children().add(new ConfigScreenList.LabeledEntry(humanKey + " : " + value)); + entry = NumberEntry.create(value, humanKey, configValue, valueSpec); } + + if (entry == null) + entry = new LabeledEntry("Impl missing - " + configValue.get().getClass().getSimpleName() + " " + humanKey + " : " + value); + + if (highlights.contains(key)) + entry.annotations.put("highlight", ":)"); + + list.children().add(entry); } }); @@ -281,13 +307,14 @@ public class SubMenuConfigScreen extends ConfigScreen { return group; }); + list.search(highlights.stream().findFirst().orElse("")); + //extras for server configs if (type != ModConfig.Type.SERVER) return; if (minecraft.hasSingleplayerServer()) return; - list.isForServer = true; boolean canEdit = minecraft != null && minecraft.player != null && minecraft.player.hasPermissions(2); Couple red = Theme.p(Theme.Key.BUTTON_FAIL); @@ -355,6 +382,12 @@ public class SubMenuConfigScreen extends ConfigScreen { if (super.keyPressed(code, p_keyPressed_2_, p_keyPressed_3_)) return true; + if (Screen.hasControlDown()) { + if (code == GLFW.GLFW_KEY_F) { + search.setFocus(true); + } + } + if (code == GLFW.GLFW_KEY_BACKSPACE) { attemptBackstep(); } @@ -362,8 +395,16 @@ public class SubMenuConfigScreen extends ConfigScreen { return false; } + private void updateFilter(String search) { + if (list.search(search)) { + this.search.setTextColor(Theme.i(Theme.Key.TEXT)); + } else { + this.search.setTextColor(Theme.i(Theme.Key.BUTTON_FAIL)); + } + } + private void attemptBackstep() { - if (changes.isEmpty() || !(parent instanceof BaseConfigScreen)) { + if (ConfigHelper.changes.isEmpty() || !(parent instanceof BaseConfigScreen)) { ScreenOpener.open(parent); return; } @@ -373,7 +414,7 @@ public class SubMenuConfigScreen extends ConfigScreen { return; if (success == Response.Confirm) saveChanges(); - changes.clear(); + ConfigHelper.changes.clear(); ScreenOpener.open(parent); }; @@ -382,7 +423,7 @@ public class SubMenuConfigScreen extends ConfigScreen { @Override public void onClose() { - if (changes.isEmpty()) { + if (ConfigHelper.changes.isEmpty()) { super.onClose(); ScreenOpener.open(parent); return; @@ -393,7 +434,7 @@ public class SubMenuConfigScreen extends ConfigScreen { return; if (success == Response.Confirm) saveChanges(); - changes.clear(); + ConfigHelper.changes.clear(); super.onClose(); }; @@ -401,11 +442,37 @@ public class SubMenuConfigScreen extends ConfigScreen { } public void showLeavingPrompt(Consumer action) { - new ConfirmationScreen().centered() - .addText(ITextProperties.of("Leaving with " + changes.size() + " unsaved change" - + (changes.size() != 1 ? "s" : "") + " for this config")) + ConfirmationScreen screen = new ConfirmationScreen() + .centered() .withThreeActions(action) - .open(this); + .addText(ITextProperties.of("Leaving with " + ConfigHelper.changes.size() + " unsaved change" + + (ConfigHelper.changes.size() != 1 ? "s" : "") + " for this config")); + + addAnnotationsToConfirm(screen).open(this); + } + + private ConfirmationScreen addAnnotationsToConfirm(ConfirmationScreen screen) { + AtomicBoolean relog = new AtomicBoolean(false); + AtomicBoolean restart = new AtomicBoolean(false); + ConfigHelper.changes.values().forEach(change -> { + if (change.annotations.containsKey(ConfigAnnotations.RequiresRelog.TRUE.getName())) + relog.set(true); + + if (change.annotations.containsKey(ConfigAnnotations.RequiresRestart.CLIENT.getName())) + restart.set(true); + }); + + if (relog.get()) { + screen.addText(ITextProperties.of(" ")); + screen.addText(ITextProperties.of("At least one changed value will require you to relog to take full effect")); + } + + if (restart.get()) { + screen.addText(ITextProperties.of(" ")); + screen.addText(ITextProperties.of("At least one changed value will require you to restart your game to take full effect")); + } + + return screen; } } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/entries/EnumEntry.java b/src/main/java/com/simibubi/create/foundation/config/ui/entries/EnumEntry.java index 2c82b98bf..e72d74552 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/entries/EnumEntry.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/entries/EnumEntry.java @@ -3,6 +3,7 @@ package com.simibubi.create.foundation.config.ui.entries; import java.util.Locale; import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.config.ui.ConfigScreen; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.BoxElement; import com.simibubi.create.foundation.gui.DelegatedStencilElement; @@ -103,10 +104,6 @@ public class EnumEntry extends ValueEntry> { @Override public void onValueChange(Enum newValue) { super.onValueChange(newValue); - valueText.withText(newValue.name() - .substring(0, 1) - + newValue.name() - .substring(1) - .toLowerCase(Locale.ROOT)); + valueText.withText(ConfigScreen.toHumanReadable(newValue.name().toLowerCase(Locale.ROOT))); } } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/entries/NumberEntry.java b/src/main/java/com/simibubi/create/foundation/config/ui/entries/NumberEntry.java index 8e5badc41..add4c54d2 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/entries/NumberEntry.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/entries/NumberEntry.java @@ -39,7 +39,7 @@ public abstract class NumberEntry extends ValueEntry { public NumberEntry(String label, ForgeConfigSpec.ConfigValue value, ForgeConfigSpec.ValueSpec spec) { super(label, value, spec); - textField = new ConfigTextField(Minecraft.getInstance().font, 0, 0, 200, 20, unit); + textField = new ConfigTextField(Minecraft.getInstance().font, 0, 0, 200, 20); if (this instanceof IntegerEntry && annotations.containsKey("IntDisplay")) { String intDisplay = annotations.get("IntDisplay"); int intValue = (Integer) getValue(); diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java b/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java index 15731cf82..f19d48d7a 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java @@ -1,19 +1,16 @@ package com.simibubi.create.foundation.config.ui.entries; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nonnull; -import org.apache.commons.lang3.ArrayUtils; - import com.google.common.base.Predicates; import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.config.ui.ConfigAnnotations; import com.simibubi.create.foundation.config.ui.ConfigHelper; import com.simibubi.create.foundation.config.ui.ConfigScreen; import com.simibubi.create.foundation.config.ui.ConfigScreenList; @@ -21,25 +18,20 @@ import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.DelegatedStencilElement; import com.simibubi.create.foundation.gui.widgets.BoxWidget; import com.simibubi.create.foundation.item.TooltipHelper; +import com.simibubi.create.foundation.utility.Pair; -import net.minecraft.util.text.IFormattableTextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.common.ForgeConfigSpec; public class ValueEntry extends ConfigScreenList.LabeledEntry { - protected static final IFormattableTextComponent modComponent = new StringTextComponent("* ").withStyle(TextFormatting.BOLD, TextFormatting.DARK_BLUE).append(StringTextComponent.EMPTY.plainCopy().withStyle(TextFormatting.RESET)); protected static final int resetWidth = 28;//including 6px offset on either side - public static final Pattern unitPattern = Pattern.compile("\\[(in .*)]"); - public static final Pattern annotationPattern = Pattern.compile("\\[@cui:([^:]*)(?::(.*))?]"); protected ForgeConfigSpec.ConfigValue value; protected ForgeConfigSpec.ValueSpec spec; - protected Map annotations; protected BoxWidget resetButton; protected boolean editable = true; - protected String path; public ValueEntry(String label, ForgeConfigSpec.ConfigValue value, ForgeConfigSpec.ValueSpec spec) { super(label); @@ -57,50 +49,36 @@ public class ValueEntry extends ConfigScreenList.LabeledEntry { listeners.add(resetButton); - annotations = new HashMap<>(); List path = value.getPath(); labelTooltip.add(new StringTextComponent(label).withStyle(TextFormatting.WHITE)); String comment = spec.getComment(); if (comment == null || comment.isEmpty()) return; - String[] commentLines = comment.split("\n"); - //find unit in the comment - for (int i = 0; i < commentLines.length; i++) { - if (commentLines[i].isEmpty()) { - commentLines = ArrayUtils.remove(commentLines, i); - i--; - continue; - } - Matcher matcher = annotationPattern.matcher(commentLines[i]); - if (matcher.matches()) { - String annotation = matcher.group(1); - String aValue = matcher.group(2); - annotations.putIfAbsent(annotation, aValue); + List commentLines = new ArrayList<>(Arrays.asList(comment.split("\n"))); - commentLines = ArrayUtils.remove(commentLines, i); - i--; - continue; - } - - matcher = unitPattern.matcher(commentLines[i]); - if (matcher.matches()) { - String u = matcher.group(1); - if (u.equals("in Revolutions per Minute")) - u = "in RPM"; - if (u.equals("in Stress Units")) - u = "in SU"; - unit = u; - } + Pair> metadata = ConfigHelper.readMetadataFromComment(commentLines); + if (metadata.getFirst() != null) { + unit = metadata.getFirst(); + } + if (metadata.getSecond() != null && !metadata.getSecond().isEmpty()) { + annotations.putAll(metadata.getSecond()); } // add comment to tooltip - labelTooltip.addAll(Arrays.stream(commentLines) + labelTooltip.addAll(commentLines.stream() .filter(Predicates.not(s -> s.startsWith("Range"))) .map(StringTextComponent::new) .flatMap(stc -> TooltipHelper.cutTextComponent(stc, TextFormatting.GRAY, TextFormatting.GRAY) .stream()) .collect(Collectors.toList())); + + if (annotations.containsKey(ConfigAnnotations.RequiresRelog.TRUE.getName())) + labelTooltip.addAll(TooltipHelper.cutTextComponent(new StringTextComponent("Changing this value will require a _relog_ to take full effect"), TextFormatting.GRAY, TextFormatting.GOLD)); + + if (annotations.containsKey(ConfigAnnotations.RequiresRestart.CLIENT.getName())) + labelTooltip.addAll(TooltipHelper.cutTextComponent(new StringTextComponent("Changing this value will require a _restart_ to take full effect"), TextFormatting.GRAY, TextFormatting.RED)); + labelTooltip.add(new StringTextComponent(ConfigScreen.modID + ":" + path.get(path.size() - 1)).withStyle(TextFormatting.DARK_GRAY)); } @@ -119,15 +97,7 @@ public class ValueEntry extends ConfigScreenList.LabeledEntry { @Override public void render(MatrixStack ms, int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean p_230432_9_, float partialTicks) { - if (isCurrentValueChanged()) { - IFormattableTextComponent original = label.getComponent(); - IFormattableTextComponent changed = modComponent.plainCopy().append(original); - label.withText(changed); - super.render(ms, index, y, x, width, height, mouseX, mouseY, p_230432_9_, partialTicks); - label.withText(original); - } else { - super.render(ms, index, y, x, width, height, mouseX, mouseY, p_230432_9_, partialTicks); - } + super.render(ms, index, y, x, width, height, mouseX, mouseY, p_230432_9_, partialTicks); resetButton.x = x + width - resetWidth + 6; resetButton.y = y + 10; @@ -140,7 +110,7 @@ public class ValueEntry extends ConfigScreenList.LabeledEntry { } public void setValue(@Nonnull T value) { - ConfigHelper.setValue(path, this.value, value); + ConfigHelper.setValue(path, this.value, value, annotations); onValueChange(value); } @@ -149,10 +119,6 @@ public class ValueEntry extends ConfigScreenList.LabeledEntry { return ConfigHelper.getValue(path, this.value); } - protected boolean isCurrentValueChanged() { - return ConfigScreen.changes.containsKey(path); - } - protected boolean isCurrentValueDefault() { return spec.getDefault().equals(getValue()); } diff --git a/src/main/java/com/simibubi/create/foundation/gui/UIRenderHelper.java b/src/main/java/com/simibubi/create/foundation/gui/UIRenderHelper.java index 60514a793..fa35d9228 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/UIRenderHelper.java +++ b/src/main/java/com/simibubi/create/foundation/gui/UIRenderHelper.java @@ -102,6 +102,22 @@ public class UIRenderHelper { ms.popPose(); } + public static void streak(MatrixStack ms, float angle, int x, int y, int breadth, int length, Color c) { + Color color = c.copy().setImmutable(); + int c1 = color.scaleAlpha(0.625f).getRGB(); + int c2 = color.scaleAlpha(0.5f).getRGB(); + int c3 = color.scaleAlpha(0.0625f).getRGB(); + int c4 = color.scaleAlpha(0f).getRGB(); + + ms.pushPose(); + ms.translate(x, y, 0); + ms.mulPose(Vector3f.ZP.rotationDegrees(angle - 90)); + + streak(ms, breadth / 2, length, c1, c2, c3, c4); + + ms.popPose(); + } + private static void streak(MatrixStack ms, int width, int height, int c1, int c2, int c3, int c4) { double split1 = .5; double split2 = .75;