Browse Source

0.3.0 lists

Unknown 6 năm trước cách đây
mục cha
commit
0878b3e102
34 tập tin đã thay đổi với 1451 bổ sung9 xóa
  1. 1 1
      README.md
  2. 1 1
      gradle.properties
  3. 4 0
      src/main/java/me/shedaniel/clothconfig2/ClothConfigInitializer.java
  4. 10 0
      src/main/java/me/shedaniel/clothconfig2/api/ConfigEntryBuilder.java
  5. 23 0
      src/main/java/me/shedaniel/clothconfig2/api/MouseUtils.java
  6. 15 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListCell.java
  7. 213 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListEntry.java
  8. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/BooleanListEntry.java
  9. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/DoubleListEntry.java
  10. 140 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/DoubleListListEntry.java
  11. 4 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/EnumListEntry.java
  12. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/FloatListEntry.java
  13. 140 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/FloatListListEntry.java
  14. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerListEntry.java
  15. 140 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerListListEntry.java
  16. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerSliderEntry.java
  17. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/LongListEntry.java
  18. 140 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/LongListListEntry.java
  19. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/LongSliderEntry.java
  20. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/StringListEntry.java
  21. 91 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/StringListListEntry.java
  22. 3 2
      src/main/java/me/shedaniel/clothconfig2/gui/entries/SubCategoryListEntry.java
  23. 3 0
      src/main/java/me/shedaniel/clothconfig2/gui/entries/TextListEntry.java
  24. 3 3
      src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicSmoothScrollingEntryListWidget.java
  25. 4 2
      src/main/java/me/shedaniel/clothconfig2/impl/ConfigBuilderImpl.java
  26. 25 0
      src/main/java/me/shedaniel/clothconfig2/impl/ConfigEntryBuilderImpl.java
  27. 98 0
      src/main/java/me/shedaniel/clothconfig2/impl/builders/DoubleListBuilder.java
  28. 98 0
      src/main/java/me/shedaniel/clothconfig2/impl/builders/FloatListBuilder.java
  29. 98 0
      src/main/java/me/shedaniel/clothconfig2/impl/builders/IntListBuilder.java
  30. 98 0
      src/main/java/me/shedaniel/clothconfig2/impl/builders/LongListBuilder.java
  31. 73 0
      src/main/java/me/shedaniel/clothconfig2/impl/builders/StringListBuilder.java
  32. 2 0
      src/main/resources/assets/cloth-config2/lang/en_us.json
  33. BIN
      src/main/resources/assets/cloth-config2/textures/gui/cloth_config.png
  34. BIN
      src/main/resources/icon.png

+ 1 - 1
README.md

@@ -5,7 +5,7 @@ repositories {
     maven { url "https://minecraft.curseforge.com/api/maven"}
 }
 dependencies {
-    mmodCompile "cloth-config:ClothConfig2:{RANDOMVERSION}"
+    modCompile "cloth-config:ClothConfig2:{RANDOMVERSION}"
 }
 ```
 ## APIs

+ 1 - 1
gradle.properties

@@ -2,5 +2,5 @@ minecraft_version=1.14.2
 yarn_version=1.14.2+build.2
 fabric_loader_version=0.4.7+build.147
 fabric_version=0.3.0-pre+build.165
-mod_version=0.2.0
+mod_version=0.3.0
 modmenu_version=1.5.4-85

+ 4 - 0
src/main/java/me/shedaniel/clothconfig2/ClothConfigInitializer.java

@@ -13,6 +13,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.Optional;
 
 public class ClothConfigInitializer implements ClientModInitializer {
@@ -33,6 +34,8 @@ public class ClothConfigInitializer implements ClientModInitializer {
                     playZone.addEntry(entryBuilder.startBooleanToggle("Simple Boolean", false).buildEntry());
                     playZone.addEntry(entryBuilder.startTextField("Simple Boolean", "ab").setDefaultValue(() -> "ab").buildEntry());
                     playZone.addEntry(entryBuilder.startLongSlider("Long Slider", 0, -10, 10).setDefaultValue(() -> 0l).buildEntry());
+                    playZone.addEntry(entryBuilder.startIntList("Int List", Arrays.asList(1, 6, 14, 1414)).setTooltip("this is a bad tooltip").setSaveConsumer(integers -> integers.forEach(System.out::println)).setDefaultValue(Arrays.asList(1, 6, 14, 1414)).build());
+                    playZone.addEntry(entryBuilder.startStrList("Party Member List", Arrays.asList("Tim", "Daniel", "John")).setTooltip("A list of party members.").setDefaultValue(Arrays.asList("Tim", "Daniel", "John")).build());
                     playZone.addEntry(entryBuilder.startIntField("Integer Field", 2).setDefaultValue(() -> 2).setMin(2).setMax(99).buildEntry());
                     SubCategoryBuilder randomCategory = entryBuilder.startSubCategory("Random Sub-Category");
                     randomCategory.add(entryBuilder.startTextDescription("§7This is a promotional message brought to you by Danielshe. Shop your favorite Lil Tater at store.liltater.com!").setTooltipSupplier(() -> Optional.of(new String[]{"This is an example tooltip."})).buildEntry());
@@ -43,6 +46,7 @@ public class ClothConfigInitializer implements ClientModInitializer {
                     ConfigCategory enumZone = builder.getOrCreateCategory("Enum Zone");
                     enumZone.setCategoryBackground(new Identifier("minecraft:textures/block/stone.png"));
                     enumZone.addEntry(entryBuilder.startEnumSelector("Enum Field", DemoEnum.class, DemoEnum.CONSTANT_2).setDefaultValue(() -> DemoEnum.CONSTANT_1).buildEntry());
+                    ConfigCategory partyZone = builder.getOrCreateCategory("Party Zone");
                     MinecraftClient.getInstance().openScreen(builder.build());
                 });
             } catch (Exception e) {

+ 10 - 0
src/main/java/me/shedaniel/clothconfig2/api/ConfigEntryBuilder.java

@@ -15,6 +15,16 @@ public interface ConfigEntryBuilder {
     
     ConfigEntryBuilder setResetButtonKey(String resetButtonKey);
     
+    IntListBuilder startIntList(String fieldNameKey, List<Integer> value);
+    
+    LongListBuilder startLongList(String fieldNameKey, List<Long> value);
+    
+    FloatListBuilder startFloatList(String fieldNameKey, List<Float> value);
+    
+    DoubleListBuilder startDoubleList(String fieldNameKey, List<Double> value);
+    
+    StringListBuilder startStrList(String fieldNameKey, List<String> value);
+    
     SubCategoryBuilder startSubCategory(String fieldNameKey);
     
     SubCategoryBuilder startSubCategory(String fieldNameKey, List<AbstractConfigListEntry> entries);

+ 23 - 0
src/main/java/me/shedaniel/clothconfig2/api/MouseUtils.java

@@ -0,0 +1,23 @@
+package me.shedaniel.clothconfig2.api;
+
+import net.minecraft.client.MinecraftClient;
+
+import java.awt.*;
+
+public interface MouseUtils {
+    
+    static MinecraftClient client = MinecraftClient.getInstance();
+    
+    static Point getMouseLocation() {
+        return new Point((int) getMouseX(), (int) getMouseY());
+    }
+    
+    static double getMouseX() {
+        return client.mouse.getX() * (double) client.window.getScaledWidth() / (double) client.window.getWidth();
+    }
+    
+    static double getMouseY() {
+        return client.mouse.getY() * (double) client.window.getScaledWidth() / (double) client.window.getWidth();
+    }
+    
+}

+ 15 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListCell.java

@@ -0,0 +1,15 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.gui.AbstractParentElement;
+
+import java.util.Optional;
+
+public abstract class BaseListCell extends AbstractParentElement {
+    
+    public abstract Optional<String> getError();
+    
+    public abstract int getCellHeight();
+    
+    public abstract void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta);
+    
+}

+ 213 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListEntry.java

@@ -0,0 +1,213 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import com.google.common.collect.Lists;
+import com.mojang.blaze3d.platform.GlStateManager;
+import me.shedaniel.clothconfig2.api.QueuedTooltip;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.util.Identifier;
+
+import java.awt.*;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class BaseListEntry<T, C extends BaseListCell> extends TooltipListEntry<List<T>> {
+    
+    protected static final Identifier CONFIG_TEX = new Identifier("cloth-config2", "textures/gui/cloth_config.png");
+    protected final List<C> cells;
+    protected final List<Element> widgets;
+    protected boolean expended;
+    protected Consumer<List<T>> saveConsumer;
+    protected ListLabelWidget labelWidget;
+    protected AbstractButtonWidget resetWidget;
+    protected Function<BaseListEntry, C> createNewInstance;
+    protected Supplier<List<T>> defaultValue;
+    protected String addTooltip = I18n.translate("text.cloth-config.list.add"), removeTooltip = I18n.translate("text.cloth-config.list.remove");
+    
+    public BaseListEntry(String fieldName, Supplier<Optional<String[]>> tooltipSupplier, Supplier<List<T>> defaultValue, Function<BaseListEntry, C> createNewInstance, Consumer<List<T>> saveConsumer, String resetButtonKey) {
+        super(fieldName, tooltipSupplier);
+        this.cells = Lists.newArrayList();
+        this.labelWidget = new ListLabelWidget();
+        this.widgets = Lists.newArrayList(labelWidget);
+        this.resetWidget = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(I18n.translate(resetButtonKey)) + 6, 20, I18n.translate(resetButtonKey), widget -> {
+            widgets.removeAll(cells);
+            cells.clear();
+            defaultValue.get().stream().map(this::getFromValue).forEach(cells::add);
+            widgets.addAll(cells);
+            getScreen().setEdited(true);
+        });
+        this.widgets.add(resetWidget);
+        this.saveConsumer = saveConsumer;
+        this.createNewInstance = createNewInstance;
+        this.defaultValue = defaultValue;
+    }
+    
+    protected abstract C getFromValue(T value);
+    
+    public Function<BaseListEntry, C> getCreateNewInstance() {
+        return createNewInstance;
+    }
+    
+    public void setCreateNewInstance(Function<BaseListEntry, C> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+    }
+    
+    public String getAddTooltip() {
+        return addTooltip;
+    }
+    
+    public void setAddTooltip(String addTooltip) {
+        this.addTooltip = addTooltip;
+    }
+    
+    public String getRemoveTooltip() {
+        return removeTooltip;
+    }
+    
+    public void setRemoveTooltip(String removeTooltip) {
+        this.removeTooltip = removeTooltip;
+    }
+    
+    @Override
+    public Optional<List<T>> getDefaultValue() {
+        return Optional.ofNullable(defaultValue.get());
+    }
+    
+    @Override
+    public int getItemHeight() {
+        if (expended) {
+            int i = 24;
+            for(BaseListCell entry : cells)
+                i += entry.getCellHeight();
+            return i;
+        }
+        return 24;
+    }
+    
+    @Override
+    public List<? extends Element> children() {
+        return widgets;
+    }
+    
+    @Override
+    public Optional<String> getError() {
+        String error = null;
+        for(BaseListCell entry : cells)
+            if (entry.getError().isPresent()) {
+                if (error != null)
+                    return Optional.ofNullable(I18n.translate("text.cloth-config.multi_error"));
+                return Optional.ofNullable((String) entry.getError().get());
+            }
+        return Optional.ofNullable(error);
+    }
+    
+    @Override
+    public void save() {
+        if (saveConsumer != null)
+            saveConsumer.accept(getValue());
+    }
+    
+    @Override
+    public boolean isMouseInside(int mouseX, int mouseY, int x, int y, int entryWidth, int entryHeight) {
+        labelWidget.rectangle.x = x - 15;
+        labelWidget.rectangle.y = y;
+        labelWidget.rectangle.width = entryWidth + 15;
+        labelWidget.rectangle.height = 24;
+        return labelWidget.rectangle.contains(mouseX, mouseY) && getParent().isMouseOver(mouseX, mouseY) && !resetWidget.isMouseOver(mouseX, mouseY);
+    }
+    
+    protected boolean isInsideCreateNew(double mouseX, double mouseY) {
+        return mouseX >= labelWidget.rectangle.x + 12 && mouseY >= labelWidget.rectangle.y + 3 && mouseX <= labelWidget.rectangle.x + 12 + 11 && mouseY <= labelWidget.rectangle.y + 3 + 11;
+    }
+    
+    protected boolean isInsideDelete(double mouseX, double mouseY) {
+        return mouseX >= labelWidget.rectangle.x + 25 && mouseY >= labelWidget.rectangle.y + 3 && mouseX <= labelWidget.rectangle.x + 25 + 11 && mouseY <= labelWidget.rectangle.y + 3 + 11;
+    }
+    
+    public Optional<String[]> getTooltip(int mouseX, int mouseY) {
+        if (addTooltip != null && isInsideCreateNew(mouseX, mouseY))
+            return Optional.of(new String[]{addTooltip});
+        if (removeTooltip != null && isInsideDelete(mouseX, mouseY))
+            return Optional.of(new String[]{removeTooltip});
+        if (getTooltipSupplier() != null)
+            return getTooltipSupplier().get();
+        return Optional.empty();
+    }
+    
+    @Override
+    public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+        labelWidget.rectangle.x = x - 19;
+        labelWidget.rectangle.y = y;
+        labelWidget.rectangle.width = entryWidth + 19;
+        labelWidget.rectangle.height = 24;
+        if (isMouseInside(mouseX, mouseY, x, y, entryWidth, entryHeight)) {
+            Optional<String[]> tooltip = getTooltip(mouseX, mouseY);
+            if (tooltip.isPresent() && tooltip.get().length > 0)
+                getScreen().queueTooltip(QueuedTooltip.create(new Point(mouseX, mouseY), tooltip.get()));
+        }
+        MinecraftClient.getInstance().getTextureManager().bindTexture(CONFIG_TEX);
+        GuiLighting.disable();
+        GlStateManager.color4f(1, 1, 1, 1);
+        BaseListCell focused = !expended || getFocused() == null || !(getFocused() instanceof BaseListCell) ? null : (BaseListCell) getFocused();
+        boolean insideCreateNew = isInsideCreateNew(mouseX, mouseY);
+        boolean insideDelete = isInsideDelete(mouseX, mouseY);
+        blit(x - 15, y + 4, 24 + 9, (labelWidget.rectangle.contains(mouseX, mouseY) && !insideCreateNew && !insideDelete ? 18 : 0) + (expended ? 9 : 0), 9, 9);
+        blit(x - 15 + 13, y + 4, 24 + 18, insideCreateNew ? 9 : 0, 9, 9);
+        blit(x - 15 + 26, y + 4, 24 + 27, focused == null ? 0 : insideDelete ? 18 : 9, 9, 9);
+        resetWidget.x = x + entryWidth - resetWidget.getWidth();
+        resetWidget.y = y;
+        resetWidget.active = isEditable() && getDefaultValue().isPresent();
+        resetWidget.render(mouseX, mouseY, delta);
+        MinecraftClient.getInstance().textRenderer.drawWithShadow(I18n.translate(getFieldName()), x + 24, y + 5, labelWidget.rectangle.contains(mouseX, mouseY) && !resetWidget.isMouseOver(mouseX, mouseY) && !insideDelete && !insideCreateNew ? 0xffe6fe16 : -1);
+        if (expended) {
+            int yy = y + 24;
+            for(BaseListCell cell : cells) {
+                cell.render(-1, yy, x + 14, entryWidth - 14, cell.getCellHeight(), mouseX, mouseY, getParent().getFocused() != null && getParent().getFocused().equals(this) && getFocused() != null && getFocused().equals(cell), delta);
+                yy += cell.getCellHeight();
+            }
+        }
+    }
+    
+    public class ListLabelWidget implements Element {
+        private Rectangle rectangle = new Rectangle();
+        
+        @Override
+        public boolean mouseClicked(double double_1, double double_2, int int_1) {
+            if (resetWidget.isMouseOver(double_1, double_2)) {
+                return false;
+            } else if (isInsideCreateNew(double_1, double_2)) {
+                expended = true;
+                C cell;
+                cells.add(0, cell = createNewInstance.apply(BaseListEntry.this));
+                widgets.add(0, cell);
+                getScreen().setEdited(true);
+                MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                return true;
+            } else if (isInsideDelete(double_1, double_2)) {
+                BaseListCell focused = !expended && getFocused() == null || !(getFocused() instanceof BaseListCell) ? null : (BaseListCell) getFocused();
+                if (focused != null) {
+                    cells.remove(focused);
+                    widgets.remove(focused);
+                    getScreen().setEdited(true);
+                    MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                }
+                return true;
+            } else if (rectangle.contains(double_1, double_2)) {
+                expended = !expended;
+                MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                return true;
+            }
+            return false;
+        }
+    }
+    
+}

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/BooleanListEntry.java

@@ -21,14 +21,17 @@ public class BooleanListEntry extends TooltipListEntry<Boolean> {
     private Supplier<Boolean> defaultValue;
     private List<Element> widgets;
     
+    @Deprecated
     public BooleanListEntry(String fieldName, boolean bool, Consumer<Boolean> saveConsumer) {
         this(fieldName, bool, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public BooleanListEntry(String fieldName, boolean bool, String resetButtonKey, Supplier<Boolean> defaultValue, Consumer<Boolean> saveConsumer) {
         this(fieldName, bool, resetButtonKey, defaultValue, saveConsumer, null);
     }
     
+    @Deprecated
     public BooleanListEntry(String fieldName, boolean bool, String resetButtonKey, Supplier<Boolean> defaultValue, Consumer<Boolean> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, tooltipSupplier);
         this.defaultValue = defaultValue;

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/DoubleListEntry.java

@@ -24,10 +24,12 @@ public class DoubleListEntry extends TextFieldListEntry<Double> {
     private double minimum, maximum;
     private Consumer<Double> saveConsumer;
     
+    @Deprecated
     public DoubleListEntry(String fieldName, Double value, Consumer<Double> saveConsumer) {
         this(fieldName, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public DoubleListEntry(String fieldName, Double value, String resetButtonKey, Supplier<Double> defaultValue, Consumer<Double> saveConsumer) {
         super(fieldName, value, resetButtonKey, defaultValue);
         this.minimum = -Double.MAX_VALUE;
@@ -35,6 +37,7 @@ public class DoubleListEntry extends TextFieldListEntry<Double> {
         this.saveConsumer = saveConsumer;
     }
     
+    @Deprecated
     public DoubleListEntry(String fieldName, Double value, String resetButtonKey, Supplier<Double> defaultValue, Consumer<Double> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, value, resetButtonKey, defaultValue, tooltipSupplier);
         this.minimum = -Double.MAX_VALUE;

+ 140 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/DoubleListListEntry.java

@@ -0,0 +1,140 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.resource.language.I18n;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class DoubleListListEntry extends BaseListEntry<Double, DoubleListListEntry.DoubleListCell> {
+    
+    private double minimum, maximum;
+    
+    @Deprecated
+    public DoubleListListEntry(String fieldName, List<Double> value, boolean defaultExpended, Supplier<Optional<String[]>> tooltipSupplier, Consumer<List<Double>> saveConsumer, Supplier<List<Double>> defaultValue, String resetButtonKey) {
+        super(fieldName, tooltipSupplier, defaultValue, baseListEntry -> new DoubleListCell(0d, (DoubleListListEntry) baseListEntry), saveConsumer, resetButtonKey);
+        this.minimum = -Double.MAX_VALUE;
+        this.maximum = Double.MAX_VALUE;
+        for(Double f : value)
+            cells.add(new DoubleListCell(f, this));
+        this.widgets.addAll(cells);
+        expended = defaultExpended;
+    }
+    
+    @Override
+    public List<Double> getValue() {
+        return cells.stream().map(cell -> Double.valueOf(cell.widget.getText())).collect(Collectors.toList());
+    }
+    
+    public DoubleListListEntry setMaximum(Double maximum) {
+        this.maximum = maximum;
+        return this;
+    }
+    
+    public DoubleListListEntry setMinimum(Double minimum) {
+        this.minimum = minimum;
+        return this;
+    }
+    
+    @Override
+    protected DoubleListCell getFromValue(Double value) {
+        return new DoubleListCell(value, this);
+    }
+    
+    public static class DoubleListCell extends BaseListCell {
+        
+        private Function<String, String> stripCharacters = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4)
+                if (Character.isDigit(var2[var4]) || var2[var4] == '-' || var2[var4] == '.')
+                    stringBuilder_1.append(var2[var4]);
+            
+            return stringBuilder_1.toString();
+        };
+        private TextFieldWidget widget;
+        private boolean isSelected;
+        private DoubleListListEntry listListEntry;
+        
+        public DoubleListCell(double value, DoubleListListEntry listListEntry) {
+            this.listListEntry = listListEntry;
+            widget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 100, 18, "") {
+                @Override
+                public void render(int int_1, int int_2, float Double_1) {
+                    boolean f = isFocused();
+                    setFocused(isSelected);
+                    try {
+                        double i = Double.valueOf(getText());
+                        if (i < listListEntry.minimum || i > listListEntry.maximum)
+                            widget.setEditableColor(16733525);
+                        else
+                            widget.setEditableColor(14737632);
+                    } catch (NumberFormatException ex) {
+                        widget.setEditableColor(16733525);
+                    }
+                    super.render(int_1, int_2, Double_1);
+                    setFocused(f);
+                }
+                
+                @Override
+                public void addText(String string_1) {
+                    super.addText(stripCharacters.apply(string_1));
+                }
+            };
+            widget.setMaxLength(999999);
+            widget.setHasBorder(false);
+            widget.setText(value + "");
+            widget.setChangedListener(s -> {
+                if (!(value + "").equalsIgnoreCase(s))
+                    listListEntry.getScreen().setEdited(true);
+            });
+        }
+        
+        @Override
+        public Optional<String> getError() {
+            try {
+                double i = Double.valueOf(widget.getText());
+                if (i > listListEntry.maximum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_large", listListEntry.maximum));
+                else if (i < listListEntry.minimum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_small", listListEntry.minimum));
+            } catch (NumberFormatException ex) {
+                return Optional.of(I18n.translate("text.cloth-config.error.not_valid_number_Double"));
+            }
+            return Optional.empty();
+        }
+        
+        @Override
+        public int getCellHeight() {
+            return 20;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            widget.setWidth(entryWidth - 12);
+            widget.x = x;
+            widget.y = y + 1;
+            widget.setIsEditable(listListEntry.isEditable());
+            this.isSelected = isSelected;
+            widget.render(mouseX, mouseY, delta);
+            if (isSelected && listListEntry.isEditable())
+                fill(x, y + 12, x + entryWidth - 12, y + 13, getError().isPresent() ? 0xffff5555 : 0xffe0e0e0);
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return Collections.singletonList(widget);
+        }
+        
+    }
+    
+}

+ 4 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/EnumListEntry.java

@@ -26,18 +26,22 @@ public class EnumListEntry<T extends Enum<?>> extends TooltipListEntry<T> {
     private List<Element> widgets;
     private Function<Enum, String> enumNameProvider;
     
+    @Deprecated
     public EnumListEntry(String fieldName, Class<T> clazz, T value, Consumer<T> saveConsumer) {
         this(fieldName, clazz, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public EnumListEntry(String fieldName, Class<T> clazz, T value, String resetButtonKey, Supplier<T> defaultValue, Consumer<T> saveConsumer) {
         this(fieldName, clazz, value, resetButtonKey, defaultValue, saveConsumer, DEFAULT_NAME_PROVIDER);
     }
     
+    @Deprecated
     public EnumListEntry(String fieldName, Class<T> clazz, T value, String resetButtonKey, Supplier<T> defaultValue, Consumer<T> saveConsumer, Function<Enum, String> enumNameProvider) {
         this(fieldName, clazz, value, resetButtonKey, defaultValue, saveConsumer, enumNameProvider, null);
     }
     
+    @Deprecated
     public EnumListEntry(String fieldName, Class<T> clazz, T value, String resetButtonKey, Supplier<T> defaultValue, Consumer<T> saveConsumer, Function<Enum, String> enumNameProvider, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, tooltipSupplier);
         T[] valuesArray = clazz.getEnumConstants();

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/FloatListEntry.java

@@ -24,10 +24,12 @@ public class FloatListEntry extends TextFieldListEntry<Float> {
     private float minimum, maximum;
     private Consumer<Float> saveConsumer;
     
+    @Deprecated
     public FloatListEntry(String fieldName, Float value, Consumer<Float> saveConsumer) {
         this(fieldName, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public FloatListEntry(String fieldName, Float value, String resetButtonKey, Supplier<Float> defaultValue, Consumer<Float> saveConsumer) {
         super(fieldName, value, resetButtonKey, defaultValue);
         this.minimum = -Float.MAX_VALUE;
@@ -35,6 +37,7 @@ public class FloatListEntry extends TextFieldListEntry<Float> {
         this.saveConsumer = saveConsumer;
     }
     
+    @Deprecated
     public FloatListEntry(String fieldName, Float value, String resetButtonKey, Supplier<Float> defaultValue, Consumer<Float> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, value, resetButtonKey, defaultValue, tooltipSupplier);
         this.minimum = -Float.MAX_VALUE;

+ 140 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/FloatListListEntry.java

@@ -0,0 +1,140 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.resource.language.I18n;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class FloatListListEntry extends BaseListEntry<Float, FloatListListEntry.FloatListCell> {
+    
+    private float minimum, maximum;
+    
+    @Deprecated
+    public FloatListListEntry(String fieldName, List<Float> value, boolean defaultExpended, Supplier<Optional<String[]>> tooltipSupplier, Consumer<List<Float>> saveConsumer, Supplier<List<Float>> defaultValue, String resetButtonKey) {
+        super(fieldName, tooltipSupplier, defaultValue, baseListEntry -> new FloatListCell(0, (FloatListListEntry) baseListEntry), saveConsumer, resetButtonKey);
+        this.minimum = -Float.MAX_VALUE;
+        this.maximum = Float.MAX_VALUE;
+        for(float f : value)
+            cells.add(new FloatListCell(f, this));
+        this.widgets.addAll(cells);
+        expended = defaultExpended;
+    }
+    
+    @Override
+    public List<Float> getValue() {
+        return cells.stream().map(cell -> Float.valueOf(cell.widget.getText())).collect(Collectors.toList());
+    }
+    
+    public FloatListListEntry setMaximum(float maximum) {
+        this.maximum = maximum;
+        return this;
+    }
+    
+    public FloatListListEntry setMinimum(float minimum) {
+        this.minimum = minimum;
+        return this;
+    }
+    
+    @Override
+    protected FloatListCell getFromValue(Float value) {
+        return new FloatListCell(value, this);
+    }
+    
+    public static class FloatListCell extends BaseListCell {
+        
+        private Function<String, String> stripCharacters = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4)
+                if (Character.isDigit(var2[var4]) || var2[var4] == '-' || var2[var4] == '.')
+                    stringBuilder_1.append(var2[var4]);
+            
+            return stringBuilder_1.toString();
+        };
+        private TextFieldWidget widget;
+        private boolean isSelected;
+        private FloatListListEntry listListEntry;
+        
+        public FloatListCell(float value, FloatListListEntry listListEntry) {
+            this.listListEntry = listListEntry;
+            widget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 100, 18, "") {
+                @Override
+                public void render(int int_1, int int_2, float float_1) {
+                    boolean f = isFocused();
+                    setFocused(isSelected);
+                    try {
+                        float i = Float.valueOf(getText());
+                        if (i < listListEntry.minimum || i > listListEntry.maximum)
+                            widget.setEditableColor(16733525);
+                        else
+                            widget.setEditableColor(14737632);
+                    } catch (NumberFormatException ex) {
+                        widget.setEditableColor(16733525);
+                    }
+                    super.render(int_1, int_2, float_1);
+                    setFocused(f);
+                }
+                
+                @Override
+                public void addText(String string_1) {
+                    super.addText(stripCharacters.apply(string_1));
+                }
+            };
+            widget.setMaxLength(999999);
+            widget.setHasBorder(false);
+            widget.setText(value + "");
+            widget.setChangedListener(s -> {
+                if (!(value + "").equalsIgnoreCase(s))
+                    listListEntry.getScreen().setEdited(true);
+            });
+        }
+        
+        @Override
+        public Optional<String> getError() {
+            try {
+                float i = Float.valueOf(widget.getText());
+                if (i > listListEntry.maximum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_large", listListEntry.maximum));
+                else if (i < listListEntry.minimum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_small", listListEntry.minimum));
+            } catch (NumberFormatException ex) {
+                return Optional.of(I18n.translate("text.cloth-config.error.not_valid_number_float"));
+            }
+            return Optional.empty();
+        }
+        
+        @Override
+        public int getCellHeight() {
+            return 20;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            widget.setWidth(entryWidth - 12);
+            widget.x = x;
+            widget.y = y + 1;
+            widget.setIsEditable(listListEntry.isEditable());
+            this.isSelected = isSelected;
+            widget.render(mouseX, mouseY, delta);
+            if (isSelected && listListEntry.isEditable())
+                fill(x, y + 12, x + entryWidth - 12, y + 13, getError().isPresent() ? 0xffff5555 : 0xffe0e0e0);
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return Collections.singletonList(widget);
+        }
+        
+    }
+    
+}

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerListEntry.java

@@ -24,10 +24,12 @@ public class IntegerListEntry extends TextFieldListEntry<Integer> {
     private int minimum, maximum;
     private Consumer<Integer> saveConsumer;
     
+    @Deprecated
     public IntegerListEntry(String fieldName, Integer value, Consumer<Integer> saveConsumer) {
         this(fieldName, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public IntegerListEntry(String fieldName, Integer value, String resetButtonKey, Supplier<Integer> defaultValue, Consumer<Integer> saveConsumer) {
         super(fieldName, value, resetButtonKey, defaultValue);
         this.minimum = -Integer.MAX_VALUE;
@@ -35,6 +37,7 @@ public class IntegerListEntry extends TextFieldListEntry<Integer> {
         this.saveConsumer = saveConsumer;
     }
     
+    @Deprecated
     public IntegerListEntry(String fieldName, Integer value, String resetButtonKey, Supplier<Integer> defaultValue, Consumer<Integer> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, value, resetButtonKey, defaultValue, tooltipSupplier);
         this.minimum = -Integer.MAX_VALUE;

+ 140 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerListListEntry.java

@@ -0,0 +1,140 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.resource.language.I18n;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class IntegerListListEntry extends BaseListEntry<Integer, IntegerListListEntry.IntegerListCell> {
+    
+    private int minimum, maximum;
+    
+    @Deprecated
+    public IntegerListListEntry(String fieldName, List<Integer> value, boolean defaultExpended, Supplier<Optional<String[]>> tooltipSupplier, Consumer<List<Integer>> saveConsumer, Supplier<List<Integer>> defaultValue, String resetButtonKey) {
+        super(fieldName, tooltipSupplier, defaultValue, baseListEntry -> new IntegerListCell(0, (IntegerListListEntry) baseListEntry), saveConsumer, resetButtonKey);
+        this.minimum = -Integer.MAX_VALUE;
+        this.maximum = Integer.MAX_VALUE;
+        for(int integer : value)
+            cells.add(new IntegerListCell(integer, this));
+        this.widgets.addAll(cells);
+        expended = defaultExpended;
+    }
+    
+    @Override
+    public List<Integer> getValue() {
+        return cells.stream().map(cell -> Integer.valueOf(cell.widget.getText())).collect(Collectors.toList());
+    }
+    
+    public IntegerListListEntry setMaximum(int maximum) {
+        this.maximum = maximum;
+        return this;
+    }
+    
+    public IntegerListListEntry setMinimum(int minimum) {
+        this.minimum = minimum;
+        return this;
+    }
+    
+    @Override
+    protected IntegerListCell getFromValue(Integer value) {
+        return new IntegerListCell(value, this);
+    }
+    
+    public static class IntegerListCell extends BaseListCell {
+        
+        private Function<String, String> stripCharacters = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4)
+                if (Character.isDigit(var2[var4]) || var2[var4] == '-')
+                    stringBuilder_1.append(var2[var4]);
+            
+            return stringBuilder_1.toString();
+        };
+        private TextFieldWidget widget;
+        private boolean isSelected;
+        private IntegerListListEntry listListEntry;
+        
+        public IntegerListCell(int value, IntegerListListEntry listListEntry) {
+            this.listListEntry = listListEntry;
+            widget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 100, 18, "") {
+                @Override
+                public void render(int int_1, int int_2, float float_1) {
+                    boolean f = isFocused();
+                    setFocused(isSelected);
+                    try {
+                        int i = Integer.valueOf(getText());
+                        if (i < listListEntry.minimum || i > listListEntry.maximum)
+                            widget.setEditableColor(16733525);
+                        else
+                            widget.setEditableColor(14737632);
+                    } catch (NumberFormatException ex) {
+                        widget.setEditableColor(16733525);
+                    }
+                    super.render(int_1, int_2, float_1);
+                    setFocused(f);
+                }
+                
+                @Override
+                public void addText(String string_1) {
+                    super.addText(stripCharacters.apply(string_1));
+                }
+            };
+            widget.setMaxLength(999999);
+            widget.setHasBorder(false);
+            widget.setText(value + "");
+            widget.setChangedListener(s -> {
+                if (!(value + "").equalsIgnoreCase(s))
+                    listListEntry.getScreen().setEdited(true);
+            });
+        }
+        
+        @Override
+        public Optional<String> getError() {
+            try {
+                int i = Integer.valueOf(widget.getText());
+                if (i > listListEntry.maximum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_large", listListEntry.maximum));
+                else if (i < listListEntry.minimum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_small", listListEntry.minimum));
+            } catch (NumberFormatException ex) {
+                return Optional.of(I18n.translate("text.cloth-config.error.not_valid_number_int"));
+            }
+            return Optional.empty();
+        }
+        
+        @Override
+        public int getCellHeight() {
+            return 20;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            widget.setWidth(entryWidth - 12);
+            widget.x = x;
+            widget.y = y + 1;
+            widget.setIsEditable(listListEntry.isEditable());
+            this.isSelected = isSelected;
+            widget.render(mouseX, mouseY, delta);
+            if (isSelected && listListEntry.isEditable())
+                fill(x, y + 12, x + entryWidth - 12, y + 13, getError().isPresent() ? 0xffff5555 : 0xffe0e0e0);
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return Collections.singletonList(widget);
+        }
+        
+    }
+    
+}

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/IntegerSliderEntry.java

@@ -27,14 +27,17 @@ public class IntegerSliderEntry extends TooltipListEntry<Integer> {
     private Function<Integer, String> textGetter = integer -> String.format("Value: %d", integer);
     private List<Element> widgets;
     
+    @Deprecated
     public IntegerSliderEntry(String fieldName, int minimum, int maximum, int value, Consumer<Integer> saveConsumer) {
         this(fieldName, minimum, maximum, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public IntegerSliderEntry(String fieldName, int minimum, int maximum, int value, String resetButtonKey, Supplier<Integer> defaultValue, Consumer<Integer> saveConsumer) {
         this(fieldName, minimum, maximum, value, resetButtonKey, defaultValue, saveConsumer, null);
     }
     
+    @Deprecated
     public IntegerSliderEntry(String fieldName, int minimum, int maximum, int value, String resetButtonKey, Supplier<Integer> defaultValue, Consumer<Integer> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, tooltipSupplier);
         this.defaultValue = defaultValue;

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/LongListEntry.java

@@ -24,10 +24,12 @@ public class LongListEntry extends TextFieldListEntry<Long> {
     private long minimum, maximum;
     private Consumer<Long> saveConsumer;
     
+    @Deprecated
     public LongListEntry(String fieldName, Long value, Consumer<Long> saveConsumer) {
         this(fieldName, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public LongListEntry(String fieldName, Long value, String resetButtonKey, Supplier<Long> defaultValue, Consumer<Long> saveConsumer) {
         super(fieldName, value, resetButtonKey, defaultValue);
         this.minimum = -Long.MAX_VALUE;
@@ -35,6 +37,7 @@ public class LongListEntry extends TextFieldListEntry<Long> {
         this.saveConsumer = saveConsumer;
     }
     
+    @Deprecated
     public LongListEntry(String fieldName, Long value, String resetButtonKey, Supplier<Long> defaultValue, Consumer<Long> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, value, resetButtonKey, defaultValue, tooltipSupplier);
         this.minimum = -Long.MAX_VALUE;

+ 140 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/LongListListEntry.java

@@ -0,0 +1,140 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.resource.language.I18n;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class LongListListEntry extends BaseListEntry<Long, LongListListEntry.LongListCell> {
+    
+    private long minimum, maximum;
+    
+    @Deprecated
+    public LongListListEntry(String fieldName, List<Long> value, boolean defaultExpended, Supplier<Optional<String[]>> tooltipSupplier, Consumer<List<Long>> saveConsumer, Supplier<List<Long>> defaultValue, String resetButtonKey) {
+        super(fieldName, tooltipSupplier, defaultValue, baseListEntry -> new LongListCell(0, (LongListListEntry) baseListEntry), saveConsumer, resetButtonKey);
+        this.minimum = -Long.MAX_VALUE;
+        this.maximum = Long.MAX_VALUE;
+        for(long l : value)
+            cells.add(new LongListCell(l, this));
+        this.widgets.addAll(cells);
+        expended = defaultExpended;
+    }
+    
+    @Override
+    public List<Long> getValue() {
+        return cells.stream().map(cell -> Long.valueOf(cell.widget.getText())).collect(Collectors.toList());
+    }
+    
+    public LongListListEntry setMaximum(long maximum) {
+        this.maximum = maximum;
+        return this;
+    }
+    
+    public LongListListEntry setMinimum(long minimum) {
+        this.minimum = minimum;
+        return this;
+    }
+    
+    @Override
+    protected LongListCell getFromValue(Long value) {
+        return new LongListCell(value, this);
+    }
+    
+    public static class LongListCell extends BaseListCell {
+        
+        private Function<String, String> stripCharacters = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4)
+                if (Character.isDigit(var2[var4]) || var2[var4] == '-')
+                    stringBuilder_1.append(var2[var4]);
+            
+            return stringBuilder_1.toString();
+        };
+        private TextFieldWidget widget;
+        private boolean isSelected;
+        private LongListListEntry listListEntry;
+        
+        public LongListCell(long value, LongListListEntry listListEntry) {
+            this.listListEntry = listListEntry;
+            widget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 100, 18, "") {
+                @Override
+                public void render(int int_1, int int_2, float float_1) {
+                    boolean f = isFocused();
+                    setFocused(isSelected);
+                    try {
+                        long l = Long.valueOf(getText());
+                        if (l < listListEntry.minimum || l > listListEntry.maximum)
+                            widget.setEditableColor(16733525);
+                        else
+                            widget.setEditableColor(14737632);
+                    } catch (NumberFormatException ex) {
+                        widget.setEditableColor(16733525);
+                    }
+                    super.render(int_1, int_2, float_1);
+                    setFocused(f);
+                }
+                
+                @Override
+                public void addText(String string_1) {
+                    super.addText(stripCharacters.apply(string_1));
+                }
+            };
+            widget.setMaxLength(999999);
+            widget.setHasBorder(false);
+            widget.setText(value + "");
+            widget.setChangedListener(s -> {
+                if (!(value + "").equalsIgnoreCase(s))
+                    listListEntry.getScreen().setEdited(true);
+            });
+        }
+        
+        @Override
+        public Optional<String> getError() {
+            try {
+                long l = Long.valueOf(widget.getText());
+                if (l > listListEntry.maximum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_large", listListEntry.maximum));
+                else if (l < listListEntry.minimum)
+                    return Optional.of(I18n.translate("text.cloth-config.error.too_small", listListEntry.minimum));
+            } catch (NumberFormatException ex) {
+                return Optional.of(I18n.translate("text.cloth-config.error.not_valid_number_long"));
+            }
+            return Optional.empty();
+        }
+        
+        @Override
+        public int getCellHeight() {
+            return 20;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            widget.setWidth(entryWidth - 12);
+            widget.x = x;
+            widget.y = y + 1;
+            widget.setIsEditable(listListEntry.isEditable());
+            this.isSelected = isSelected;
+            widget.render(mouseX, mouseY, delta);
+            if (isSelected && listListEntry.isEditable())
+                fill(x, y + 12, x + entryWidth - 12, y + 13, getError().isPresent() ? 0xffff5555 : 0xffe0e0e0);
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return Collections.singletonList(widget);
+        }
+        
+    }
+    
+}

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/LongSliderEntry.java

@@ -27,14 +27,17 @@ public class LongSliderEntry extends TooltipListEntry<Long> {
     private Function<Long, String> textGetter = value -> String.format("Value: %d", value);
     private List<Element> widgets;
     
+    @Deprecated
     public LongSliderEntry(String fieldName, long minimum, long maximum, long value, Consumer<Long> saveConsumer) {
         this(fieldName, minimum, maximum, value, saveConsumer, "text.cloth-config.reset_value", null);
     }
     
+    @Deprecated
     public LongSliderEntry(String fieldName, long minimum, long maximum, long value, Consumer<Long> saveConsumer, String resetButtonKey, Supplier<Long> defaultValue) {
         this(fieldName, minimum, maximum, value, saveConsumer, resetButtonKey, defaultValue, null);
     }
     
+    @Deprecated
     public LongSliderEntry(String fieldName, long minimum, long maximum, long value, Consumer<Long> saveConsumer, String resetButtonKey, Supplier<Long> defaultValue, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, tooltipSupplier);
         this.defaultValue = defaultValue;

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/StringListEntry.java

@@ -8,15 +8,18 @@ public class StringListEntry extends TextFieldListEntry<String> {
     
     private Consumer<String> saveConsumer;
     
+    @Deprecated
     public StringListEntry(String fieldName, String value, Consumer<String> saveConsumer) {
         this(fieldName, value, "text.cloth-config.reset_value", null, saveConsumer);
     }
     
+    @Deprecated
     public StringListEntry(String fieldName, String value, String resetButtonKey, Supplier<String> defaultValue, Consumer<String> saveConsumer) {
         super(fieldName, value, resetButtonKey, defaultValue);
         this.saveConsumer = saveConsumer;
     }
     
+    @Deprecated
     public StringListEntry(String fieldName, String value, String resetButtonKey, Supplier<String> defaultValue, Consumer<String> saveConsumer, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, value, resetButtonKey, defaultValue, tooltipSupplier);
         this.saveConsumer = saveConsumer;

+ 91 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/StringListListEntry.java

@@ -0,0 +1,91 @@
+package me.shedaniel.clothconfig2.gui.entries;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class StringListListEntry extends BaseListEntry<String, StringListListEntry.StringListCell> {
+    
+    @Deprecated
+    public StringListListEntry(String fieldName, List<String> value, boolean defaultExpended, Supplier<Optional<String[]>> tooltipSupplier, Consumer<List<String>> saveConsumer, Supplier<List<String>> defaultValue, String resetButtonKey) {
+        super(fieldName, tooltipSupplier, defaultValue, baseListEntry -> new StringListCell("", (StringListListEntry) baseListEntry), saveConsumer, resetButtonKey);
+        for(String str : value)
+            cells.add(new StringListCell(str, this));
+        this.widgets.addAll(cells);
+        expended = defaultExpended;
+    }
+    
+    @Override
+    public List<String> getValue() {
+        return cells.stream().map(cell -> cell.widget.getText()).collect(Collectors.toList());
+    }
+    
+    @Override
+    protected StringListCell getFromValue(String value) {
+        return new StringListCell(value, this);
+    }
+    
+    public static class StringListCell extends BaseListCell {
+        
+        private TextFieldWidget widget;
+        private boolean isSelected;
+        private StringListListEntry listListEntry;
+        
+        public StringListCell(String value, StringListListEntry listListEntry) {
+            this.listListEntry = listListEntry;
+            widget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 100, 18, "") {
+                @Override
+                public void render(int int_1, int int_2, float float_1) {
+                    boolean f = isFocused();
+                    setFocused(isSelected);
+                    widget.setEditableColor(14737632);
+                    super.render(int_1, int_2, float_1);
+                    setFocused(f);
+                }
+            };
+            widget.setMaxLength(999999);
+            widget.setHasBorder(false);
+            widget.setText(value);
+            widget.setChangedListener(s -> {
+                if (!value.contentEquals(s))
+                    listListEntry.getScreen().setEdited(true);
+            });
+        }
+        
+        @Override
+        public Optional<String> getError() {
+            return Optional.empty();
+        }
+        
+        @Override
+        public int getCellHeight() {
+            return 20;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            widget.setWidth(entryWidth - 12);
+            widget.x = x;
+            widget.y = y + 1;
+            widget.setIsEditable(listListEntry.isEditable());
+            this.isSelected = isSelected;
+            widget.render(mouseX, mouseY, delta);
+            if (isSelected && listListEntry.isEditable())
+                fill(x, y + 12, x + entryWidth - 12, y + 13, getError().isPresent() ? 0xffff5555 : 0xffe0e0e0);
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return Collections.singletonList(widget);
+        }
+        
+    }
+    
+}

+ 3 - 2
src/main/java/me/shedaniel/clothconfig2/gui/entries/SubCategoryListEntry.java

@@ -24,6 +24,7 @@ public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigLi
     private List<Element> children;
     private boolean expended;
     
+    @Deprecated
     public SubCategoryListEntry(String categoryName, List<AbstractConfigListEntry> entries, boolean defaultExpended) {
         super(categoryName, null);
         this.categoryName = categoryName;
@@ -58,8 +59,8 @@ public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigLi
         MinecraftClient.getInstance().getTextureManager().bindTexture(CONFIG_TEX);
         GuiLighting.disable();
         GlStateManager.color4f(1, 1, 1, 1);
-        blit(x - 15, y + 4, 24, expended ? 9 : 0, 9, 9);
-        MinecraftClient.getInstance().textRenderer.drawWithShadow(I18n.translate(categoryName), x, y + 5, -1);
+        blit(x - 15, y + 4, 24, (widget.rectangle.contains(mouseX, mouseY) ? 18 : 0) + (expended ? 9 : 0), 9, 9);
+        MinecraftClient.getInstance().textRenderer.drawWithShadow(I18n.translate(categoryName), x, y + 5, widget.rectangle.contains(mouseX, mouseY) ? 0xffe6fe16 : -1);
         for(AbstractConfigListEntry entry : entries) {
             entry.setParent(getParent());
             entry.setScreen(getScreen());

+ 3 - 0
src/main/java/me/shedaniel/clothconfig2/gui/entries/TextListEntry.java

@@ -14,14 +14,17 @@ public class TextListEntry extends TooltipListEntry {
     private int color;
     private String text;
     
+    @Deprecated
     public TextListEntry(String fieldName, String text) {
         this(fieldName, text, -1);
     }
     
+    @Deprecated
     public TextListEntry(String fieldName, String text, int color) {
         this(fieldName, text, color, null);
     }
     
+    @Deprecated
     public TextListEntry(String fieldName, String text, int color, Supplier<Optional<String[]>> tooltipSupplier) {
         super(fieldName, tooltipSupplier);
         this.text = text;

+ 3 - 3
src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicSmoothScrollingEntryListWidget.java

@@ -119,10 +119,10 @@ public abstract class DynamicSmoothScrollingEntryListWidget<E extends DynamicEnt
                 scrollVelocity -= 16;
             return true;
         }
-        if (scroll >= 0 && scroll <= getMaxScroll()) {
-            if (double_3 < 0)
+        if (true) {
+            if (scroll <= getMaxScroll() && double_3 < 0)
                 scrollVelocity += 16;
-            if (double_3 > 0)
+            if (scroll >= 0 && double_3 > 0)
                 scrollVelocity -= 16;
             return true;
         }

+ 4 - 2
src/main/java/me/shedaniel/clothconfig2/impl/ConfigBuilderImpl.java

@@ -7,6 +7,7 @@ import me.shedaniel.clothconfig2.api.ConfigCategory;
 import me.shedaniel.clothconfig2.gui.ClothConfigScreen;
 import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.resource.language.I18n;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Pair;
 
@@ -177,10 +178,11 @@ public class ConfigBuilderImpl implements ConfigBuilder {
     
     @Override
     public Screen build() {
-        ClothConfigScreen screen = new ClothConfigScreen(parent, title, dataMap, doesConfirmSave, doesProcessErrors, listSmoothScroll, defaultBackground, categoryBackground) {
+        ClothConfigScreen screen = new ClothConfigScreen(parent, I18n.translate(title), dataMap, doesConfirmSave, doesProcessErrors, listSmoothScroll, defaultBackground, categoryBackground) {
             @Override
             public void onSave(Map<String, List<Pair<String, Object>>> o) {
-                savingRunnable.run();
+                if (savingRunnable != null)
+                    savingRunnable.run();
             }
             
             @Override

+ 25 - 0
src/main/java/me/shedaniel/clothconfig2/impl/ConfigEntryBuilderImpl.java

@@ -29,6 +29,31 @@ public class ConfigEntryBuilderImpl implements ConfigEntryBuilder {
         return this;
     }
     
+    @Override
+    public IntListBuilder startIntList(String fieldNameKey, List<Integer> value) {
+        return new IntListBuilder(resetButtonKey, fieldNameKey, value);
+    }
+    
+    @Override
+    public LongListBuilder startLongList(String fieldNameKey, List<Long> value) {
+        return new LongListBuilder(resetButtonKey, fieldNameKey, value);
+    }
+    
+    @Override
+    public FloatListBuilder startFloatList(String fieldNameKey, List<Float> value) {
+        return new FloatListBuilder(resetButtonKey, fieldNameKey, value);
+    }
+    
+    @Override
+    public DoubleListBuilder startDoubleList(String fieldNameKey, List<Double> value) {
+        return new DoubleListBuilder(resetButtonKey, fieldNameKey, value);
+    }
+    
+    @Override
+    public StringListBuilder startStrList(String fieldNameKey, List<String> value) {
+        return new StringListBuilder(resetButtonKey, fieldNameKey, value);
+    }
+    
     @Override
     public SubCategoryBuilder startSubCategory(String fieldNameKey) {
         return new SubCategoryBuilder(resetButtonKey, fieldNameKey);

+ 98 - 0
src/main/java/me/shedaniel/clothconfig2/impl/builders/DoubleListBuilder.java

@@ -0,0 +1,98 @@
+package me.shedaniel.clothconfig2.impl.builders;
+
+import me.shedaniel.clothconfig2.gui.entries.BaseListEntry;
+import me.shedaniel.clothconfig2.gui.entries.DoubleListListEntry;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class DoubleListBuilder extends FieldBuilder<List<Double>, DoubleListListEntry> {
+    
+    private Consumer<List<Double>> saveConsumer = null;
+    private Supplier<Optional<String[]>> tooltipSupplier = null;
+    private List<Double> value;
+    private boolean expended = false;
+    private Double min = null, max = null;
+    private Function<BaseListEntry, DoubleListListEntry.DoubleListCell> createNewInstance;
+    
+    public DoubleListBuilder(String resetButtonKey, String fieldNameKey, List<Double> value) {
+        super(resetButtonKey, fieldNameKey);
+        this.value = value;
+    }
+    
+    public DoubleListBuilder setCreateNewInstance(Function<BaseListEntry, DoubleListListEntry.DoubleListCell> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+        return this;
+    }
+    
+    public DoubleListBuilder setExpended(boolean expended) {
+        this.expended = expended;
+        return this;
+    }
+    
+    public DoubleListBuilder setSaveConsumer(Consumer<List<Double>> saveConsumer) {
+        this.saveConsumer = saveConsumer;
+        return this;
+    }
+    
+    public DoubleListBuilder setDefaultValue(Supplier<List<Double>> defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+    
+    public DoubleListBuilder setMin(double min) {
+        this.min = min;
+        return this;
+    }
+    
+    public DoubleListBuilder setMax(double max) {
+        this.max = max;
+        return this;
+    }
+    
+    public DoubleListBuilder removeMin() {
+        this.min = null;
+        return this;
+    }
+    
+    public DoubleListBuilder removeMax() {
+        this.max = null;
+        return this;
+    }
+    
+    public DoubleListBuilder setDefaultValue(List<Double> defaultValue) {
+        this.defaultValue = () -> defaultValue;
+        return this;
+    }
+    
+    public DoubleListBuilder setTooltipSupplier(Supplier<Optional<String[]>> tooltipSupplier) {
+        this.tooltipSupplier = tooltipSupplier;
+        return this;
+    }
+    
+    public DoubleListBuilder setTooltip(Optional<String[]> tooltip) {
+        this.tooltipSupplier = () -> tooltip;
+        return this;
+    }
+    
+    public DoubleListBuilder setTooltip(String... tooltip) {
+        this.tooltipSupplier = () -> Optional.ofNullable(tooltip);
+        return this;
+    }
+    
+    @Override
+    public DoubleListListEntry build() {
+        DoubleListListEntry entry = new DoubleListListEntry(getFieldNameKey(), value, expended, tooltipSupplier, saveConsumer, defaultValue, getResetButtonKey());
+        if (min != null)
+            entry.setMinimum(min);
+        if (max != null)
+            entry.setMaximum(max);
+        if (createNewInstance != null)
+            entry.setCreateNewInstance(createNewInstance);
+        return entry;
+    }
+    
+}

+ 98 - 0
src/main/java/me/shedaniel/clothconfig2/impl/builders/FloatListBuilder.java

@@ -0,0 +1,98 @@
+package me.shedaniel.clothconfig2.impl.builders;
+
+import me.shedaniel.clothconfig2.gui.entries.BaseListEntry;
+import me.shedaniel.clothconfig2.gui.entries.FloatListListEntry;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class FloatListBuilder extends FieldBuilder<List<Float>, FloatListListEntry> {
+    
+    private Consumer<List<Float>> saveConsumer = null;
+    private Supplier<Optional<String[]>> tooltipSupplier = null;
+    private List<Float> value;
+    private boolean expended = false;
+    private Float min = null, max = null;
+    private Function<BaseListEntry, FloatListListEntry.FloatListCell> createNewInstance;
+    
+    public FloatListBuilder(String resetButtonKey, String fieldNameKey, List<Float> value) {
+        super(resetButtonKey, fieldNameKey);
+        this.value = value;
+    }
+    
+    public FloatListBuilder setCreateNewInstance(Function<BaseListEntry, FloatListListEntry.FloatListCell> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+        return this;
+    }
+    
+    public FloatListBuilder setExpended(boolean expended) {
+        this.expended = expended;
+        return this;
+    }
+    
+    public FloatListBuilder setSaveConsumer(Consumer<List<Float>> saveConsumer) {
+        this.saveConsumer = saveConsumer;
+        return this;
+    }
+    
+    public FloatListBuilder setDefaultValue(Supplier<List<Float>> defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+    
+    public FloatListBuilder setMin(float min) {
+        this.min = min;
+        return this;
+    }
+    
+    public FloatListBuilder setMax(float max) {
+        this.max = max;
+        return this;
+    }
+    
+    public FloatListBuilder removeMin() {
+        this.min = null;
+        return this;
+    }
+    
+    public FloatListBuilder removeMax() {
+        this.max = null;
+        return this;
+    }
+    
+    public FloatListBuilder setDefaultValue(List<Float> defaultValue) {
+        this.defaultValue = () -> defaultValue;
+        return this;
+    }
+    
+    public FloatListBuilder setTooltipSupplier(Supplier<Optional<String[]>> tooltipSupplier) {
+        this.tooltipSupplier = tooltipSupplier;
+        return this;
+    }
+    
+    public FloatListBuilder setTooltip(Optional<String[]> tooltip) {
+        this.tooltipSupplier = () -> tooltip;
+        return this;
+    }
+    
+    public FloatListBuilder setTooltip(String... tooltip) {
+        this.tooltipSupplier = () -> Optional.ofNullable(tooltip);
+        return this;
+    }
+    
+    @Override
+    public FloatListListEntry build() {
+        FloatListListEntry entry = new FloatListListEntry(getFieldNameKey(), value, expended, tooltipSupplier, saveConsumer, defaultValue, getResetButtonKey());
+        if (min != null)
+            entry.setMinimum(min);
+        if (max != null)
+            entry.setMaximum(max);
+        if (createNewInstance != null)
+            entry.setCreateNewInstance(createNewInstance);
+        return entry;
+    }
+    
+}

+ 98 - 0
src/main/java/me/shedaniel/clothconfig2/impl/builders/IntListBuilder.java

@@ -0,0 +1,98 @@
+package me.shedaniel.clothconfig2.impl.builders;
+
+import me.shedaniel.clothconfig2.gui.entries.BaseListEntry;
+import me.shedaniel.clothconfig2.gui.entries.IntegerListListEntry;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class IntListBuilder extends FieldBuilder<List<Integer>, IntegerListListEntry> {
+    
+    private Consumer<List<Integer>> saveConsumer = null;
+    private Supplier<Optional<String[]>> tooltipSupplier = null;
+    private List<Integer> value;
+    private boolean expended = false;
+    private Integer min = null, max = null;
+    private Function<BaseListEntry, IntegerListListEntry.IntegerListCell> createNewInstance;
+    
+    public IntListBuilder(String resetButtonKey, String fieldNameKey, List<Integer> value) {
+        super(resetButtonKey, fieldNameKey);
+        this.value = value;
+    }
+    
+    public IntListBuilder setCreateNewInstance(Function<BaseListEntry, IntegerListListEntry.IntegerListCell> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+        return this;
+    }
+    
+    public IntListBuilder setExpended(boolean expended) {
+        this.expended = expended;
+        return this;
+    }
+    
+    public IntListBuilder setSaveConsumer(Consumer<List<Integer>> saveConsumer) {
+        this.saveConsumer = saveConsumer;
+        return this;
+    }
+    
+    public IntListBuilder setDefaultValue(Supplier<List<Integer>> defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+    
+    public IntListBuilder setMin(int min) {
+        this.min = min;
+        return this;
+    }
+    
+    public IntListBuilder setMax(int max) {
+        this.max = max;
+        return this;
+    }
+    
+    public IntListBuilder removeMin() {
+        this.min = null;
+        return this;
+    }
+    
+    public IntListBuilder removeMax() {
+        this.max = null;
+        return this;
+    }
+    
+    public IntListBuilder setDefaultValue(List<Integer> defaultValue) {
+        this.defaultValue = () -> defaultValue;
+        return this;
+    }
+    
+    public IntListBuilder setTooltipSupplier(Supplier<Optional<String[]>> tooltipSupplier) {
+        this.tooltipSupplier = tooltipSupplier;
+        return this;
+    }
+    
+    public IntListBuilder setTooltip(Optional<String[]> tooltip) {
+        this.tooltipSupplier = () -> tooltip;
+        return this;
+    }
+    
+    public IntListBuilder setTooltip(String... tooltip) {
+        this.tooltipSupplier = () -> Optional.ofNullable(tooltip);
+        return this;
+    }
+    
+    @Override
+    public IntegerListListEntry build() {
+        IntegerListListEntry entry = new IntegerListListEntry(getFieldNameKey(), value, expended, tooltipSupplier, saveConsumer, defaultValue, getResetButtonKey());
+        if (min != null)
+            entry.setMinimum(min);
+        if (max != null)
+            entry.setMaximum(max);
+        if (createNewInstance != null)
+            entry.setCreateNewInstance(createNewInstance);
+        return entry;
+    }
+    
+}

+ 98 - 0
src/main/java/me/shedaniel/clothconfig2/impl/builders/LongListBuilder.java

@@ -0,0 +1,98 @@
+package me.shedaniel.clothconfig2.impl.builders;
+
+import me.shedaniel.clothconfig2.gui.entries.BaseListEntry;
+import me.shedaniel.clothconfig2.gui.entries.LongListListEntry;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class LongListBuilder extends FieldBuilder<List<Long>, LongListListEntry> {
+    
+    private Consumer<List<Long>> saveConsumer = null;
+    private Supplier<Optional<String[]>> tooltipSupplier = null;
+    private List<Long> value;
+    private boolean expended = false;
+    private Long min = null, max = null;
+    private Function<BaseListEntry, LongListListEntry.LongListCell> createNewInstance;
+    
+    public LongListBuilder(String resetButtonKey, String fieldNameKey, List<Long> value) {
+        super(resetButtonKey, fieldNameKey);
+        this.value = value;
+    }
+    
+    public LongListBuilder setCreateNewInstance(Function<BaseListEntry, LongListListEntry.LongListCell> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+        return this;
+    }
+    
+    public LongListBuilder setExpended(boolean expended) {
+        this.expended = expended;
+        return this;
+    }
+    
+    public LongListBuilder setSaveConsumer(Consumer<List<Long>> saveConsumer) {
+        this.saveConsumer = saveConsumer;
+        return this;
+    }
+    
+    public LongListBuilder setDefaultValue(Supplier<List<Long>> defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+    
+    public LongListBuilder setMin(long min) {
+        this.min = min;
+        return this;
+    }
+    
+    public LongListBuilder setMax(long max) {
+        this.max = max;
+        return this;
+    }
+    
+    public LongListBuilder removeMin() {
+        this.min = null;
+        return this;
+    }
+    
+    public LongListBuilder removeMax() {
+        this.max = null;
+        return this;
+    }
+    
+    public LongListBuilder setDefaultValue(List<Long> defaultValue) {
+        this.defaultValue = () -> defaultValue;
+        return this;
+    }
+    
+    public LongListBuilder setTooltipSupplier(Supplier<Optional<String[]>> tooltipSupplier) {
+        this.tooltipSupplier = tooltipSupplier;
+        return this;
+    }
+    
+    public LongListBuilder setTooltip(Optional<String[]> tooltip) {
+        this.tooltipSupplier = () -> tooltip;
+        return this;
+    }
+    
+    public LongListBuilder setTooltip(String... tooltip) {
+        this.tooltipSupplier = () -> Optional.ofNullable(tooltip);
+        return this;
+    }
+    
+    @Override
+    public LongListListEntry build() {
+        LongListListEntry entry = new LongListListEntry(getFieldNameKey(), value, expended, tooltipSupplier, saveConsumer, defaultValue, getResetButtonKey());
+        if (min != null)
+            entry.setMinimum(min);
+        if (max != null)
+            entry.setMaximum(max);
+        if (createNewInstance != null)
+            entry.setCreateNewInstance(createNewInstance);
+        return entry;
+    }
+    
+}

+ 73 - 0
src/main/java/me/shedaniel/clothconfig2/impl/builders/StringListBuilder.java

@@ -0,0 +1,73 @@
+package me.shedaniel.clothconfig2.impl.builders;
+
+import me.shedaniel.clothconfig2.gui.entries.BaseListEntry;
+import me.shedaniel.clothconfig2.gui.entries.StringListListEntry;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class StringListBuilder extends FieldBuilder<List<String>, StringListListEntry> {
+    
+    private Consumer<List<String>> saveConsumer = null;
+    private Supplier<Optional<String[]>> tooltipSupplier = null;
+    private List<String> value;
+    private boolean expended = false;
+    private Function<BaseListEntry, StringListListEntry.StringListCell> createNewInstance;
+    
+    public StringListBuilder(String resetButtonKey, String fieldNameKey, List<String> value) {
+        super(resetButtonKey, fieldNameKey);
+        this.value = value;
+    }
+    
+    public StringListBuilder setCreateNewInstance(Function<BaseListEntry, StringListListEntry.StringListCell> createNewInstance) {
+        this.createNewInstance = createNewInstance;
+        return this;
+    }
+    
+    public StringListBuilder setExpended(boolean expended) {
+        this.expended = expended;
+        return this;
+    }
+    
+    public StringListBuilder setSaveConsumer(Consumer<List<String>> saveConsumer) {
+        this.saveConsumer = saveConsumer;
+        return this;
+    }
+    
+    public StringListBuilder setDefaultValue(Supplier<List<String>> defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+    
+    public StringListBuilder setDefaultValue(List<String> defaultValue) {
+        this.defaultValue = () -> defaultValue;
+        return this;
+    }
+    
+    public StringListBuilder setTooltipSupplier(Supplier<Optional<String[]>> tooltipSupplier) {
+        this.tooltipSupplier = tooltipSupplier;
+        return this;
+    }
+    
+    public StringListBuilder setTooltip(Optional<String[]> tooltip) {
+        this.tooltipSupplier = () -> tooltip;
+        return this;
+    }
+    
+    public StringListBuilder setTooltip(String... tooltip) {
+        this.tooltipSupplier = () -> Optional.ofNullable(tooltip);
+        return this;
+    }
+    
+    @Override
+    public StringListListEntry build() {
+        StringListListEntry entry = new StringListListEntry(getFieldNameKey(), value, expended, tooltipSupplier, saveConsumer, defaultValue, getResetButtonKey());
+        if (createNewInstance != null)
+            entry.setCreateNewInstance(createNewInstance);
+        return entry;
+    }
+    
+}

+ 2 - 0
src/main/resources/assets/cloth-config2/lang/en_us.json

@@ -13,6 +13,8 @@
   "text.cloth-config.error.not_valid_number_double": "Not a valid number! (Double)",
   "text.cloth-config.error.too_large": "Too Large! (Maximum: %d)",
   "text.cloth-config.error.too_small": "Too Small! (Minimum: %d)",
+  "text.cloth-config.list.add": "Insert New",
+  "text.cloth-config.list.remove": "Delete Selected",
   "text.cloth-config.error_cannot_save": "Error!",
   "text.cloth-config.reset_value": "Reset",
   "text.cloth.reset_value": "Reset"

BIN
src/main/resources/assets/cloth-config2/textures/gui/cloth_config.png


BIN
src/main/resources/icon.png