Răsfoiți Sursa

global stuff

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 5 ani în urmă
părinte
comite
3346c4c0ed
24 a modificat fișierele cu 411 adăugiri și 175 ștergeri
  1. 8 0
      build.gradle
  2. 2 2
      gradle.properties
  3. 11 0
      src/main/java/me/shedaniel/clothconfig2/ClothConfigInitializer.java
  4. 10 2
      src/main/java/me/shedaniel/clothconfig2/api/AbstractConfigEntry.java
  5. 2 4
      src/main/java/me/shedaniel/clothconfig2/api/ConfigBuilder.java
  6. 8 0
      src/main/java/me/shedaniel/clothconfig2/api/ConfigScreen.java
  7. 7 0
      src/main/java/me/shedaniel/clothconfig2/api/Expandable.java
  8. 5 0
      src/main/java/me/shedaniel/clothconfig2/api/ReferenceProvider.java
  9. 17 1
      src/main/java/me/shedaniel/clothconfig2/gui/AbstractConfigScreen.java
  10. 20 36
      src/main/java/me/shedaniel/clothconfig2/gui/ClothConfigScreen.java
  11. 213 42
      src/main/java/me/shedaniel/clothconfig2/gui/GlobalizedClothConfigScreen.java
  12. 1 1
      src/main/java/me/shedaniel/clothconfig2/gui/entries/AbstractListListEntry.java
  13. 17 4
      src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListEntry.java
  14. 2 3
      src/main/java/me/shedaniel/clothconfig2/gui/entries/ColorEntry.java
  15. 17 1
      src/main/java/me/shedaniel/clothconfig2/gui/entries/MultiElementListEntry.java
  16. 10 1
      src/main/java/me/shedaniel/clothconfig2/gui/entries/NestedListListEntry.java
  17. 17 1
      src/main/java/me/shedaniel/clothconfig2/gui/entries/SubCategoryListEntry.java
  18. 12 11
      src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicEntryListWidget.java
  19. 12 12
      src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicNewSmoothScrollingEntryListWidget.java
  20. 14 27
      src/main/java/me/shedaniel/clothconfig2/impl/ConfigBuilderImpl.java
  21. 0 13
      src/main/java/me/shedaniel/clothconfig2/mixin/ButtonWidgetHooks.java
  22. 4 0
      src/main/resources/cloth-config2.accessWidener
  23. 2 4
      src/main/resources/fabric.mod.json
  24. 0 10
      src/main/resources/mixin.cloth-config2.json

+ 8 - 0
build.gradle

@@ -25,7 +25,12 @@ group = "me.shedaniel.cloth"
 archivesBaseName = "config-2"
 version = project.mod_version
 
+repositories {
+    maven { url "https://dl.bintray.com/shedaniel/shedaniel-mods" }
+}
+
 minecraft {
+    accessWidener = file("src/main/resources/cloth-config2.accessWidener")
 }
 
 processResources {
@@ -49,6 +54,9 @@ dependencies {
 
     modCompileOnly("io.github.prospector:modmenu:${modmenu_version}")
     modRuntime("io.github.prospector:modmenu:${modmenu_version}")
+    modRuntime("me.shedaniel:SmoothScrollingEverywhere:3.0.3-unstable") {
+        transitive(false)
+    }
 }
 
 task jarFilter(type: net.corda.gradle.jarfilter.JarFilterTask) {

+ 2 - 2
gradle.properties

@@ -1,7 +1,7 @@
 supported_version=1.16-pre2
 minecraft_version=1.16-pre2
-yarn_mappings=1.16-pre2+build.1+legacy.20w09a+build.8
+yarn_mappings=1.16-pre2+build.2+legacy.20w09a+build.8-fix.1
 loader_version=0.8.7+build.201
 fabric_version=0.11.6+build.355-1.16
-mod_version=4.5.0-unstable
+mod_version=4.5.1
 modmenu_version=1.11.8+build.13

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

@@ -131,6 +131,7 @@ public class ClothConfigInitializer implements ClientModInitializer {
         
         ConfigBuilder builder = ConfigBuilder.create().setParentScreen(MinecraftClient.getInstance().currentScreen).setTitle(new TranslatableText("title.cloth-config.config"));
         builder.setDefaultBackgroundTexture(new Identifier("minecraft:textures/block/oak_planks.png"));
+        builder.setGlobalized(true);
         ConfigEntryBuilder entryBuilder = builder.entryBuilder();
         ConfigCategory testing = builder.getOrCreateCategory(new TranslatableText("category.cloth-config.testing"));
         testing.addEntry(entryBuilder.startKeyCodeField(new LiteralText("Cool Key"), InputUtil.UNKNOWN_KEYCODE).setDefaultValue(InputUtil.UNKNOWN_KEYCODE).build());
@@ -147,6 +148,16 @@ public class ClothConfigInitializer implements ClientModInitializer {
         colors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
         colors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
         colors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        SubCategoryBuilder innerColors = entryBuilder.startSubCategory(new LiteralText("Inner Colors")).setExpanded(true);
+        innerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        innerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        innerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        SubCategoryBuilder innerInnerColors = entryBuilder.startSubCategory(new LiteralText("Inner Inner Colors")).setExpanded(true);
+        innerInnerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        innerInnerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        innerInnerColors.add(entryBuilder.startDropdownMenu(new LiteralText("lol apple"), DropdownMenuBuilder.TopCellElementBuilder.ofItemObject(Items.APPLE), DropdownMenuBuilder.CellCreatorBuilder.ofItemObject()).setDefaultValue(Items.APPLE).setSelections(Registry.ITEM.stream().sorted(Comparator.comparing(Item::toString)).collect(Collectors.toCollection(LinkedHashSet::new))).setSaveConsumer(item -> System.out.println("save this " + item)).build());
+        innerColors.add(innerInnerColors.build());
+        colors.add(innerColors.build());
         testing.addEntry(colors.build());
         testing.addEntry(entryBuilder.startDropdownMenu(new LiteralText("Suggestion Random Int"), DropdownMenuBuilder.TopCellElementBuilder.of(10,
                 s -> {

+ 10 - 2
src/main/java/me/shedaniel/clothconfig2/api/AbstractConfigEntry.java

@@ -18,7 +18,7 @@ import java.util.Optional;
 import java.util.function.Supplier;
 
 @Environment(EnvType.CLIENT)
-public abstract class AbstractConfigEntry<T> extends DynamicElementListWidget.ElementEntry<AbstractConfigEntry<T>> {
+public abstract class AbstractConfigEntry<T> extends DynamicElementListWidget.ElementEntry<AbstractConfigEntry<T>> implements ReferenceProvider<T> {
     private AbstractConfigScreen screen;
     private Supplier<Optional<Text>> errorSupplier;
     @Nullable
@@ -35,6 +35,11 @@ public abstract class AbstractConfigEntry<T> extends DynamicElementListWidget.El
         }
     }
     
+    @Override
+    public AbstractConfigEntry<T> provideReferenceEntry() {
+        return this;
+    }
+    
     @Nullable
     @ApiStatus.Internal
     public final List<AbstractConfigEntry<?>> getReferencableEntries() {
@@ -100,7 +105,6 @@ public abstract class AbstractConfigEntry<T> extends DynamicElementListWidget.El
     
     public void updateSelected(boolean isSelected) {}
     
-    @Deprecated
     @ApiStatus.Internal
     public final void setScreen(AbstractConfigScreen screen) {
         this.screen = screen;
@@ -116,4 +120,8 @@ public abstract class AbstractConfigEntry<T> extends DynamicElementListWidget.El
     public int getItemHeight() {
         return 24;
     }
+    
+    public int getInitialReferenceOffset() {
+        return 0;
+    }
 }

+ 2 - 4
src/main/java/me/shedaniel/clothconfig2/api/ConfigBuilder.java

@@ -83,12 +83,10 @@ public interface ConfigBuilder {
         return setAlwaysShowTabs(true);
     }
     
-    /**
-     * @deprecated does not work
-     */
-    @Deprecated
     void setGlobalized(boolean globalized);
     
+    void setGlobalizedExpanded(boolean globalizedExpanded);
+    
     boolean isAlwaysShowTabs();
     
     ConfigBuilder setAlwaysShowTabs(boolean alwaysShowTabs);

+ 8 - 0
src/main/java/me/shedaniel/clothconfig2/api/ConfigScreen.java

@@ -1,8 +1,16 @@
 package me.shedaniel.clothconfig2.api;
 
+import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Consumer;
 
 public interface ConfigScreen {
+    void setSavingRunnable(@Nullable Runnable savingRunnable);
+    
+    void setAfterInitConsumer(@Nullable Consumer<Screen> afterInitConsumer);
+    
     Identifier getBackgroundLocation();
     
     boolean isRequiresRestart();

+ 7 - 0
src/main/java/me/shedaniel/clothconfig2/api/Expandable.java

@@ -0,0 +1,7 @@
+package me.shedaniel.clothconfig2.api;
+
+public interface Expandable {
+    boolean isExpanded();
+    
+    void setExpanded(boolean expanded);
+}

+ 5 - 0
src/main/java/me/shedaniel/clothconfig2/api/ReferenceProvider.java

@@ -0,0 +1,5 @@
+package me.shedaniel.clothconfig2.api;
+
+public interface ReferenceProvider<T> {
+    AbstractConfigEntry<T> provideReferenceEntry();
+}

+ 17 - 1
src/main/java/me/shedaniel/clothconfig2/gui/AbstractConfigScreen.java

@@ -25,6 +25,8 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
 
 public abstract class AbstractConfigScreen extends Screen implements ConfigScreen {
     protected static final Identifier CONFIG_TEX = new Identifier("cloth-config2", "textures/gui/cloth_config.png");
@@ -42,6 +44,10 @@ public abstract class AbstractConfigScreen extends Screen implements ConfigScree
     private KeyCodeEntry focusedBinding;
     private ModifierKeyCode startedKeyCode = null;
     private final List<Tooltip> tooltips = Lists.newArrayList();
+    @Nullable
+    private Runnable savingRunnable = null;
+    @Nullable
+    protected Consumer<Screen> afterInitConsumer = null;
     
     protected AbstractConfigScreen(Screen parent, Text title, Identifier backgroundLocation) {
         super(title);
@@ -49,6 +55,16 @@ public abstract class AbstractConfigScreen extends Screen implements ConfigScree
         this.backgroundLocation = backgroundLocation;
     }
     
+    @Override
+    public void setSavingRunnable(@Nullable Runnable savingRunnable) {
+        this.savingRunnable = savingRunnable;
+    }
+    
+    @Override
+    public void setAfterInitConsumer(@Nullable Consumer<Screen> afterInitConsumer) {
+        this.afterInitConsumer = afterInitConsumer;
+    }
+    
     @Override
     public Identifier getBackgroundLocation() {
         return backgroundLocation;
@@ -162,6 +178,7 @@ public abstract class AbstractConfigScreen extends Screen implements ConfigScree
     }
     
     public void save() {
+        Optional.ofNullable(this.savingRunnable).ifPresent(Runnable::run);
     }
     
     public boolean isEditable() {
@@ -351,7 +368,6 @@ public abstract class AbstractConfigScreen extends Screen implements ConfigScree
         BufferBuilder buffer = tessellator.getBuffer();
         client.getTextureManager().bindTexture(getBackgroundLocation());
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        float f = 32.0F;
         buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
         buffer.vertex(matrix, rect.getMinX(), rect.getMaxY(), 0.0F).texture(rect.getMinX() / 32.0F, rect.getMaxY() / 32.0F).color(red, green, blue, endAlpha).next();
         buffer.vertex(matrix, rect.getMaxX(), rect.getMaxY(), 0.0F).texture(rect.getMaxX() / 32.0F, rect.getMaxY() / 32.0F).color(red, green, blue, endAlpha).next();

+ 20 - 36
src/main/java/me/shedaniel/clothconfig2/gui/ClothConfigScreen.java

@@ -11,7 +11,6 @@ import net.fabricmc.api.Environment;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.gui.widget.AbstractButtonWidget;
-import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.render.BufferBuilder;
 import net.minecraft.client.render.Tessellator;
@@ -29,11 +28,12 @@ import org.jetbrains.annotations.ApiStatus;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 @SuppressWarnings({"deprecation", "rawtypes", "DuplicatedCode"})
 @Environment(EnvType.CLIENT)
-public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
+public class ClothConfigScreen extends AbstractTabbedConfigScreen {
     private ScrollingContainer tabsScroller = new ScrollingContainer() {
         @Override
         public Rectangle getBounds() {
@@ -59,7 +59,7 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
     private double tabsMaximumScrolled = -1d;
     private final List<ClothConfigTabButton> tabButtons = Lists.newArrayList();
     
-    @Deprecated
+    @ApiStatus.Internal
     public ClothConfigScreen(Screen parent, Text title, Map<Text, List<Object>> entriesMap, Identifier backgroundLocation) {
         super(parent, title, backgroundLocation);
         entriesMap.forEach((categoryName, list) -> {
@@ -134,15 +134,8 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
             listWidget.children().addAll((List) Lists.newArrayList(categorizedEntries.values()).get(selectedCategoryIndex));
         }
         int buttonWidths = Math.min(200, (width - 50 - 12) / 3);
-        addButton(quitButton = new ButtonWidget(width / 2 - buttonWidths - 3, height - 26, buttonWidths, 20, isEdited() ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"), widget -> {
-            quit();
-        }));
-        addButton(saveButton = new AbstractPressableButtonWidget(width / 2 + 3, height - 26, buttonWidths, 20, NarratorManager.EMPTY) {
-            @Override
-            public void onPress() {
-                saveAll(true);
-            }
-            
+        addButton(quitButton = new ButtonWidget(width / 2 - buttonWidths - 3, height - 26, buttonWidths, 20, isEdited() ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"), widget -> quit()));
+        addButton(saveButton = new ButtonWidget(width / 2 + 3, height - 26, buttonWidths, 20, NarratorManager.EMPTY, button -> saveAll(true)) {
             @Override
             public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
                 boolean hasErrors = false;
@@ -165,12 +158,7 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
             tabsBounds = new Rectangle(0, 41, width, 24);
             tabsLeftBounds = new Rectangle(0, 41, 18, 24);
             tabsRightBounds = new Rectangle(width - 18, 41, 18, 24);
-            children.add(buttonLeftTab = new AbstractPressableButtonWidget(4, 44, 12, 18, NarratorManager.EMPTY) {
-                @Override
-                public void onPress() {
-                    tabsScroller.scrollTo(0, false);
-                }
-                
+            children.add(buttonLeftTab = new ButtonWidget(4, 44, 12, 18, NarratorManager.EMPTY, button -> tabsScroller.scrollTo(0, true)) {
                 @Override
                 public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
                     client.getTextureManager().bindTexture(CONFIG_TEX);
@@ -188,12 +176,7 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
                 j++;
             }
             children.addAll(tabButtons);
-            children.add(buttonRightTab = new AbstractPressableButtonWidget(width - 16, 44, 12, 18, NarratorManager.EMPTY) {
-                @Override
-                public void onPress() {
-                    tabsScroller.scrollTo(tabsScroller.getMaxScroll(), false);
-                }
-                
+            children.add(buttonRightTab = new ButtonWidget(width - 16, 44, 12, 18, NarratorManager.EMPTY, button -> tabsScroller.scrollTo(tabsScroller.getMaxScroll(), true)) {
                 @Override
                 public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
                     client.getTextureManager().bindTexture(CONFIG_TEX);
@@ -208,15 +191,16 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
         } else {
             tabsBounds = tabsLeftBounds = tabsRightBounds = new Rectangle();
         }
+        Optional.ofNullable(this.afterInitConsumer).ifPresent(consumer -> consumer.accept(this));
     }
     
     @Override
-    public boolean mouseScrolled(double double_1, double double_2, double double_3) {
-        if (tabsBounds.contains(double_1, double_2) && !tabsLeftBounds.contains(double_1, double_2) && !tabsRightBounds.contains(double_1, double_2) && double_3 != 0d) {
-            tabsScroller.offset(-double_3 * 16, true);
+    public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
+        if (tabsBounds.contains(mouseX, mouseY) && !tabsLeftBounds.contains(mouseX, mouseY) && !tabsRightBounds.contains(mouseX, mouseY) && amount != 0d) {
+            tabsScroller.offset(-amount * 16, true);
             return true;
         }
-        return super.mouseScrolled(double_1, double_2, double_3);
+        return super.mouseScrolled(mouseX, mouseY, amount);
     }
     
     public double getTabsMaximumScrolled() {
@@ -374,20 +358,20 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
         }
         
         @Override
-        public boolean mouseClicked(double double_1, double double_2, int int_1) {
-            this.updateScrollingState(double_1, double_2, int_1);
-            if (!this.isMouseOver(double_1, double_2)) {
+        public boolean mouseClicked(double mouseX, double mouseY, int button) {
+            this.updateScrollingState(mouseX, mouseY, button);
+            if (!this.isMouseOver(mouseX, mouseY)) {
                 return false;
             } else {
                 for (R entry : children()) {
-                    if (entry.mouseClicked(double_1, double_2, int_1)) {
+                    if (entry.mouseClicked(mouseX, mouseY, button)) {
                         this.setFocused(entry);
                         this.setDragging(true);
                         return true;
                     }
                 }
-                if (int_1 == 0) {
-                    this.clickedHeader((int) (double_1 - (double) (this.left + this.width / 2 - this.getItemWidth() / 2)), (int) (double_2 - (double) this.top) + (int) this.getScroll() - 4);
+                if (button == 0) {
+                    this.clickedHeader((int) (mouseX - (double) (this.left + this.width / 2 - this.getItemWidth() / 2)), (int) (mouseY - (double) this.top) + (int) this.getScroll() - 4);
                     return true;
                 }
                 
@@ -405,9 +389,9 @@ public abstract class ClothConfigScreen extends AbstractTabbedConfigScreen {
         }
         
         @Override
-        protected void renderHoleBackground(MatrixStack matrices, int int_1, int int_2, int int_3, int int_4) {
+        protected void renderHoleBackground(MatrixStack matrices, int y1, int y2, int alpha1, int alpha2) {
             if (!screen.isTransparentBackground())
-                super.renderHoleBackground(matrices, int_1, int_2, int_3, int_4);
+                super.renderHoleBackground(matrices, y1, y2, alpha1, alpha2);
         }
     }
 }

+ 213 - 42
src/main/java/me/shedaniel/clothconfig2/gui/GlobalizedClothConfigScreen.java

@@ -3,33 +3,58 @@ package me.shedaniel.clothconfig2.gui;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.mojang.blaze3d.systems.RenderSystem;
+import me.shedaniel.clothconfig2.ClothConfigInitializer;
 import me.shedaniel.clothconfig2.api.*;
 import me.shedaniel.math.Rectangle;
 import net.minecraft.class_5348;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.render.BufferBuilder;
 import net.minecraft.client.render.Tessellator;
+import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexFormats;
+import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.NarratorManager;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.LiteralText;
+import net.minecraft.text.MutableText;
 import net.minecraft.text.Text;
 import net.minecraft.text.TranslatableText;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Pair;
+import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Matrix4f;
 import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.ApiStatus;
 
 import java.util.*;
 
-public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements ReferenceBuildingConfigScreen {
+public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements ReferenceBuildingConfigScreen, Expandable {
     public ClothConfigScreen.ListWidget<AbstractConfigEntry<AbstractConfigEntry<?>>> listWidget;
+    private AbstractButtonWidget cancelButton, exitButton;
     private final LinkedHashMap<Text, List<AbstractConfigEntry<?>>> categorizedEntries = Maps.newLinkedHashMap();
+    private final ScrollingContainer sideScroller = new ScrollingContainer() {
+        @Override
+        public Rectangle getBounds() {
+            return new Rectangle(4, 4, getSideSliderPosition() - 14 - 4, height - 8);
+        }
+        
+        @Override
+        public int getMaxScrollHeight() {
+            int i = 0;
+            for (Reference reference : references) {
+                if (i != 0) i += 3 * reference.getScale();
+                i += textRenderer.fontHeight * reference.getScale();
+            }
+            return i;
+        }
+    };
+    private Reference lastHoveredReference = null;
     private final ScrollingContainer sideSlider = new ScrollingContainer() {
         private Rectangle empty = new Rectangle();
         
@@ -40,23 +65,23 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         
         @Override
         public int getMaxScrollHeight() {
-            return GlobalizedClothConfigScreen.this.sideExpandLimit.get();
+            return 1;
         }
     };
     private final List<Reference> references = Lists.newArrayList();
     private final LazyResettable<Integer> sideExpandLimit = new LazyResettable<>(() -> {
         int max = 0;
         for (Reference reference : references) {
-            Text category = reference.getText();
-            int width = textRenderer.getWidth(new LiteralText(StringUtils.repeat("    ", reference.getIndent()) + "- ").append(category));
+            Text referenceText = reference.getText();
+            int width = textRenderer.getWidth(new LiteralText(StringUtils.repeat("  ", reference.getIndent()) + "- ").append(referenceText));
             if (width > max) max = width;
         }
-        return max + 8;
+        return Math.min(max + 8, width / 4);
     });
     private boolean requestingReferenceRebuilding = false;
     
-    @Deprecated
-    protected GlobalizedClothConfigScreen(Screen parent, Text title, Map<Text, List<Object>> entriesMap, Identifier backgroundLocation) {
+    @ApiStatus.Internal
+    public GlobalizedClothConfigScreen(Screen parent, Text title, Map<Text, List<Object>> entriesMap, Identifier backgroundLocation) {
         super(parent, title, backgroundLocation);
         entriesMap.forEach((categoryName, list) -> {
             List<AbstractConfigEntry<?>> entries = Lists.newArrayList();
@@ -72,6 +97,7 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
             }
             categorizedEntries.put(categoryName, entries);
         });
+        this.sideSlider.scrollTo(0, false);
     }
     
     @Override
@@ -93,25 +119,17 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         buildReferences();
         this.children.add(listWidget = new ClothConfigScreen.ListWidget<>(this, client, width - 14, height, 30, height - 32, getBackgroundLocation()));
         this.listWidget.setLeftPos(14);
-        this.sideSlider.scrollTo(14, false);
         this.categorizedEntries.forEach((category, entries) -> {
             if (!listWidget.children().isEmpty())
                 this.listWidget.children().add((AbstractConfigEntry) new EmptyEntry(5));
             this.listWidget.children().add((AbstractConfigEntry) new EmptyEntry(4));
-            this.listWidget.children().add((AbstractConfigEntry) new TextEntry(category.shallowCopy().formatted(Formatting.BOLD)));
-            this.listWidget.children().add((AbstractConfigEntry) new EmptyEntry(2));
+            this.listWidget.children().add((AbstractConfigEntry) new CategoryTextEntry(category, category.shallowCopy().formatted(Formatting.BOLD)));
+            this.listWidget.children().add((AbstractConfigEntry) new EmptyEntry(4));
             this.listWidget.children().addAll((List) entries);
         });
         int buttonWidths = Math.min(200, (width - 50 - 12) / 3);
-        addButton(new ButtonWidget(width / 2 - buttonWidths - 3, height - 26, buttonWidths, 20, isEdited() ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"), widget -> {
-            quit();
-        }));
-        addButton(new AbstractPressableButtonWidget(width / 2 + 3, height - 26, buttonWidths, 20, NarratorManager.EMPTY) {
-            @Override
-            public void onPress() {
-                saveAll(true);
-            }
-            
+        addButton(cancelButton = new ButtonWidget(0, height - 26, buttonWidths, 20, isEdited() ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"), widget -> quit()));
+        addButton(exitButton = new ButtonWidget(0, height - 26, buttonWidths, 20, NarratorManager.EMPTY, button -> saveAll(true)) {
             @Override
             public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
                 boolean hasErrors = false;
@@ -129,12 +147,13 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
                 super.render(matrices, mouseX, mouseY, delta);
             }
         });
+        Optional.ofNullable(this.afterInitConsumer).ifPresent(consumer -> consumer.accept(this));
     }
     
     private void buildReferences() {
         categorizedEntries.forEach((categoryText, entries) -> {
             this.references.add(new CategoryReference(categoryText));
-            for (AbstractConfigEntry<?> entry : entries) buildReferenceFor(entry, 0);
+            for (AbstractConfigEntry<?> entry : entries) buildReferenceFor(entry, 1);
         });
     }
     
@@ -148,52 +167,155 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         }
     }
     
+    @SuppressWarnings("deprecation")
     @Override
     public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
+        this.lastHoveredReference = null;
         if (requestingReferenceRebuilding) {
             this.references.clear();
             buildReferences();
             requestingReferenceRebuilding = false;
         }
+        int sliderPosition = getSideSliderPosition();
+        ScissorsHandler.INSTANCE.scissor(new Rectangle(sliderPosition, 0, width - sliderPosition, height));
         if (isTransparentBackground()) {
-            fillGradient(matrices, 0, 0, this.width, this.height, -1072689136, -804253680);
+            fillGradient(matrices, 14, 0, width, height, -1072689136, -804253680);
         } else {
             renderBackgroundTexture(0);
+            overlayBackground(matrices, new Rectangle(14, 0, width, height), 64, 64, 64, 255, 255);
         }
+        listWidget.width = width - sliderPosition;
+        listWidget.setLeftPos(sliderPosition);
         listWidget.render(matrices, mouseX, mouseY, delta);
         ScissorsHandler.INSTANCE.scissor(new Rectangle(listWidget.left, listWidget.top, listWidget.width, listWidget.bottom - listWidget.top));
         for (AbstractConfigEntry<?> child : listWidget.children())
             child.lateRender(matrices, mouseX, mouseY, delta);
         ScissorsHandler.INSTANCE.removeLastScissor();
-        drawCenteredText(matrices, client.textRenderer, title, width / 2, 12, -1);
+        textRenderer.drawWithShadow(matrices, title, sliderPosition + (width - sliderPosition) / 2f - textRenderer.getWidth(title) / 2f, 12, -1);
+        ScissorsHandler.INSTANCE.removeLastScissor();
+        cancelButton.x = sliderPosition + (width - sliderPosition) / 2 - cancelButton.getWidth() - 3;
+        exitButton.x = sliderPosition + (width - sliderPosition) / 2 + 3;
         super.render(matrices, mouseX, mouseY, delta);
+        sideSlider.updatePosition(delta);
+        sideScroller.updatePosition(delta);
         if (isTransparentBackground()) {
-//            fillGradient(matrices, 0, 0, (int) sideSlider.scrollAmount, height, -1072689136, -804253680);
+            fillGradient(matrices, 0, 0, sliderPosition, height, -1240461296, -972025840);
+            fillGradient(matrices, 0, 0, sliderPosition - 14, height, 1744830464, 1744830464);
         } else {
-//            overlayBackground(matrices, new Rectangle(0, 0, (int) sideSlider.scrollAmount, height), 64, 64, 64, 255, 255);
+            Tessellator tessellator = Tessellator.getInstance();
+            BufferBuilder buffer = tessellator.getBuffer();
+            client.getTextureManager().bindTexture(getBackgroundLocation());
+            RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+            float f = 32.0F;
+            buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
+            buffer.vertex(sliderPosition - 14, height, 0.0D).texture(0, height / 32.0F).color(68, 68, 68, 255).next();
+            buffer.vertex(sliderPosition, height, 0.0D).texture(14 / 32.0F, height / 32.0F).color(68, 68, 68, 255).next();
+            buffer.vertex(sliderPosition, 0, 0.0D).texture(14 / 32.0F, 0).color(68, 68, 68, 255).next();
+            buffer.vertex(sliderPosition - 14, 0, 0.0D).texture(0, 0).color(68, 68, 68, 255).next();
+            tessellator.draw();
+            
+            buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
+            buffer.vertex(0, height, 0.0D).texture(0, (height + (int) sideScroller.scrollAmount) / 32.0F).color(32, 32, 32, 255).next();
+            buffer.vertex(sliderPosition - 14, height, 0.0D).texture((sliderPosition - 14) / 32.0F, (height + (int) sideScroller.scrollAmount) / 32.0F).color(32, 32, 32, 255).next();
+            buffer.vertex(sliderPosition - 14, 0, 0.0D).texture((sliderPosition - 14) / 32.0F, ((int) sideScroller.scrollAmount) / 32.0F).color(32, 32, 32, 255).next();
+            buffer.vertex(0, 0, 0.0D).texture(0, ((int) sideScroller.scrollAmount) / 32.0F).color(32, 32, 32, 255).next();
+            tessellator.draw();
         }
-        sideSlider.updatePosition(delta);
         {
             Matrix4f matrix = matrices.peek().getModel();
+            RenderSystem.disableTexture();
             RenderSystem.enableBlend();
-            RenderSystem.blendFuncSeparate(770, 771, 0, 1);
             RenderSystem.disableAlphaTest();
+            RenderSystem.defaultBlendFunc();
             RenderSystem.shadeModel(7425);
-            RenderSystem.disableTexture();
             Tessellator tessellator = Tessellator.getInstance();
             BufferBuilder buffer = tessellator.getBuffer();
-            buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
-            int shadeColor = isTransparentBackground() ? 120 : 255;
-            buffer.vertex(matrix, (int) sideSlider.scrollAmount + 4, height, 100.0F).texture(0, 1f).color(0, 0, 0, shadeColor).next();
-            buffer.vertex(matrix, (int) sideSlider.scrollAmount, height, 100.0F).texture(1f, 1f).color(0, 0, 0, shadeColor).next();
-            buffer.vertex(matrix, (int) sideSlider.scrollAmount, 0, 100.0F).texture(1f, 0).color(0, 0, 0, shadeColor).next();
-            buffer.vertex(matrix, (int) sideSlider.scrollAmount + 4, 0, 100.0F).texture(0, 0).color(0, 0, 0, shadeColor).next();
+            int shadeColor = isTransparentBackground() ? 120 : 160;
+            buffer.begin(7, VertexFormats.POSITION_COLOR);
+            buffer.vertex(matrix, sliderPosition + 4, 0, 100.0F).color(0, 0, 0, 0).next();
+            buffer.vertex(matrix, sliderPosition, 0, 100.0F).color(0, 0, 0, shadeColor).next();
+            buffer.vertex(matrix, sliderPosition, height, 100.0F).color(0, 0, 0, shadeColor).next();
+            buffer.vertex(matrix, sliderPosition + 4, height, 100.0F).color(0, 0, 0, 0).next();
+            tessellator.draw();
+            shadeColor /= 2;
+            buffer.begin(7, VertexFormats.POSITION_COLOR);
+            buffer.vertex(matrix, sliderPosition - 14, 0, 100.0F).color(0, 0, 0, shadeColor).next();
+            buffer.vertex(matrix, sliderPosition - 14 - 4, 0, 100.0F).color(0, 0, 0, 0).next();
+            buffer.vertex(matrix, sliderPosition - 14 - 4, height, 100.0F).color(0, 0, 0, 0).next();
+            buffer.vertex(matrix, sliderPosition - 14, height, 100.0F).color(0, 0, 0, shadeColor).next();
             tessellator.draw();
-            RenderSystem.enableTexture();
             RenderSystem.shadeModel(7424);
-            RenderSystem.enableAlphaTest();
             RenderSystem.disableBlend();
+            RenderSystem.enableAlphaTest();
+            RenderSystem.enableTexture();
         }
+        Rectangle slideArrowBounds = new Rectangle(sliderPosition - 14, 0, 14, height);
+        {
+            RenderSystem.enableAlphaTest();
+            VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
+            textRenderer.drawLayer(">", sliderPosition - 7 - textRenderer.getStringWidth(">") / 2f, height / 2, (slideArrowBounds.contains(mouseX, mouseY) ? 16777120 : 16777215) | MathHelper.clamp(MathHelper.ceil((1 - sideSlider.scrollAmount) * 255.0F), 0, 255) << 24, false, matrices.peek().getModel(), immediate, false, 0, 15728880);
+            textRenderer.drawLayer("<", sliderPosition - 7 - textRenderer.getStringWidth("<") / 2f, height / 2, (slideArrowBounds.contains(mouseX, mouseY) ? 16777120 : 16777215) | MathHelper.clamp(MathHelper.ceil(sideSlider.scrollAmount * 255.0F), 0, 255) << 24, false, matrices.peek().getModel(), immediate, false, 0, 15728880);
+            immediate.draw();
+            
+            Rectangle scrollerBounds = sideScroller.getBounds();
+            if (!scrollerBounds.isEmpty()) {
+                ScissorsHandler.INSTANCE.scissor(new Rectangle(0, 0, sliderPosition - 14, height));
+                int scrollOffset = (int) (scrollerBounds.y - sideScroller.scrollAmount);
+                for (Reference reference : references) {
+                    matrices.push();
+                    matrices.scale(reference.getScale(), reference.getScale(), reference.getScale());
+                    MutableText text = new LiteralText(StringUtils.repeat("  ", reference.getIndent()) + "- ").append(reference.getText());
+                    if (lastHoveredReference == null && new Rectangle(scrollerBounds.x, (int) (scrollOffset - 4 * reference.getScale()), (int) (textRenderer.getWidth(text) * reference.getScale()), (int) ((textRenderer.fontHeight + 4) * reference.getScale())).contains(mouseX, mouseY))
+                        lastHoveredReference = reference;
+                    textRenderer.draw(matrices, text, scrollerBounds.x, scrollOffset, lastHoveredReference == reference ? 16769544 : 16777215);
+                    matrices.pop();
+                    scrollOffset += (textRenderer.fontHeight + 3) * reference.getScale();
+                }
+                ScissorsHandler.INSTANCE.removeLastScissor();
+                sideScroller.renderScrollBar();
+            }
+        }
+    }
+    
+    @Override
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        Rectangle slideBounds = new Rectangle(0, 0, getSideSliderPosition() - 14, height);
+        if (button == 0 && slideBounds.contains(mouseX, mouseY) && lastHoveredReference != null) {
+            client.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+            lastHoveredReference.go();
+            return true;
+        }
+        Rectangle slideArrowBounds = new Rectangle(getSideSliderPosition() - 14, 0, 14, height);
+        if (button == 0 && slideArrowBounds.contains(mouseX, mouseY)) {
+            setExpanded(!isExpanded());
+            client.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+            return true;
+        }
+        return super.mouseClicked(mouseX, mouseY, button);
+    }
+    
+    @Override
+    public boolean isExpanded() {
+        return sideSlider.scrollTarget == 1;
+    }
+    
+    @Override
+    public void setExpanded(boolean expanded) {
+        this.sideSlider.scrollTo(expanded ? 1 : 0, true, 2000);
+    }
+    
+    @Override
+    public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
+        Rectangle slideBounds = new Rectangle(0, 0, getSideSliderPosition() - 14, height);
+        if (slideBounds.contains(mouseX, mouseY)) {
+            sideScroller.offset(ClothConfigInitializer.getScrollStep() * -amount, true);
+            return true;
+        }
+        return super.mouseScrolled(mouseX, mouseY, amount);
+    }
+    
+    private int getSideSliderPosition() {
+        return (int) (sideSlider.scrollAmount * sideExpandLimit.get() + 14);
     }
     
     private static class EmptyEntry extends AbstractConfigListEntry<Object> {
@@ -231,11 +353,13 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         }
     }
     
-    private static class TextEntry extends AbstractConfigListEntry<Object> {
+    private static class CategoryTextEntry extends AbstractConfigListEntry<Object> {
+        private final Text category;
         private final Text text;
         
-        public TextEntry(Text text) {
+        public CategoryTextEntry(Text category, Text text) {
             super(new LiteralText(UUID.randomUUID().toString()), false);
+            this.category = category;
             this.text = text;
         }
         
@@ -284,13 +408,15 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         Text getText();
         
         float getScale();
+        
+        void go();
     }
     
-    private static class CategoryReference implements Reference {
+    private class CategoryReference implements Reference {
         private Text category;
         
         public CategoryReference(Text category) {
-            this.category = category.shallowCopy().formatted(Formatting.BOLD);
+            this.category = category;
         }
         
         @Override
@@ -302,9 +428,21 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         public float getScale() {
             return 1.0F;
         }
+        
+        @Override
+        public void go() {
+            int i = 0;
+            for (AbstractConfigEntry<?> child : listWidget.children()) {
+                if (child instanceof CategoryTextEntry && ((CategoryTextEntry) child).category == category) {
+                    listWidget.scrollTo(i, true);
+                    return;
+                }
+                i += child.getItemHeight();
+            }
+        }
     }
     
-    private static class ConfigEntryReference implements Reference {
+    private class ConfigEntryReference implements Reference {
         private AbstractConfigEntry<?> entry;
         private int layer;
         
@@ -325,7 +463,40 @@ public class GlobalizedClothConfigScreen extends AbstractConfigScreen implements
         
         @Override
         public float getScale() {
-            return 0.5F;
+            return 1.0F;
+        }
+        
+        @Override
+        public void go() {
+            int[] i = {0};
+            for (AbstractConfigEntry<?> child : listWidget.children()) {
+                int i1 = i[0];
+                if (goChild(i, null, child)) return;
+                i[0] = i1 + child.getItemHeight();
+            }
+        }
+        
+        private boolean goChild(int[] i, Integer expandedParent, AbstractConfigEntry<?> root) {
+            if (root == entry) {
+                listWidget.scrollTo(expandedParent == null ? i[0] : expandedParent, true);
+                return true;
+            }
+            int j = i[0];
+            i[0] += root.getInitialReferenceOffset();
+            boolean expanded = root instanceof Expandable && ((Expandable) root).isExpanded();
+            if (root instanceof Expandable) ((Expandable) root).setExpanded(true);
+            List<? extends Element> children = root.children();
+            if (root instanceof Expandable) ((Expandable) root).setExpanded(expanded);
+            for (Element child : children) {
+                if (child instanceof ReferenceProvider<?>) {
+                    int i1 = i[0];
+                    if (goChild(i, expandedParent != null ? expandedParent : root instanceof Expandable && !expanded ? j : null, ((ReferenceProvider<?>) child).provideReferenceEntry())) {
+                        return true;
+                    }
+                    i[0] = i1 + ((ReferenceProvider<?>) child).provideReferenceEntry().getItemHeight();
+                }
+            }
+            return false;
         }
     }
 }

+ 1 - 1
src/main/java/me/shedaniel/clothconfig2/gui/entries/AbstractListListEntry.java

@@ -33,7 +33,7 @@ public abstract class AbstractListListEntry<T, C extends AbstractListListEntry.A
         for (T f : value)
             cells.add(createNewCell.apply(f, this.self()));
         this.widgets.addAll(cells);
-        expanded = defaultExpanded;
+        setExpanded(defaultExpanded);
     }
     
     public Function<T, Optional<Text>> getCellErrorSupplier() {

+ 17 - 4
src/main/java/me/shedaniel/clothconfig2/gui/entries/BaseListEntry.java

@@ -2,6 +2,7 @@ package me.shedaniel.clothconfig2.gui.entries;
 
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
+import me.shedaniel.clothconfig2.api.Expandable;
 import me.shedaniel.clothconfig2.api.Tooltip;
 import me.shedaniel.math.Point;
 import me.shedaniel.math.Rectangle;
@@ -38,7 +39,7 @@ import java.util.stream.Collectors;
  * @implNote See <a href="https://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable">Is there a way to refer to the current type with a type variable?</href> on Stack Overflow.
  */
 @Environment(EnvType.CLIENT)
-public abstract class BaseListEntry<T, C extends BaseListCell, SELF extends BaseListEntry<T, C, SELF>> extends TooltipListEntry<List<T>> {
+public abstract class BaseListEntry<T, C extends BaseListCell, SELF extends BaseListEntry<T, C, SELF>> extends TooltipListEntry<List<T>> implements Expandable {
     
     protected static final Identifier CONFIG_TEX = new Identifier("cloth-config2", "textures/gui/cloth_config.png");
     @NotNull protected final List<C> cells;
@@ -55,19 +56,16 @@ public abstract class BaseListEntry<T, C extends BaseListCell, SELF extends Base
     protected Text addTooltip = new TranslatableText("text.cloth-config.list.add"), removeTooltip = new TranslatableText("text.cloth-config.list.remove");
     
     @ApiStatus.Internal
-    @Deprecated
     public BaseListEntry(@NotNull Text fieldName, @Nullable Supplier<Optional<Text[]>> tooltipSupplier, @NotNull Supplier<List<T>> defaultValue, @NotNull Function<SELF, C> createNewInstance, @Nullable Consumer<List<T>> saveConsumer, Text resetButtonKey) {
         this(fieldName, tooltipSupplier, defaultValue, createNewInstance, saveConsumer, resetButtonKey, false);
     }
     
     @ApiStatus.Internal
-    @Deprecated
     public BaseListEntry(@NotNull Text fieldName, @Nullable Supplier<Optional<Text[]>> tooltipSupplier, @NotNull Supplier<List<T>> defaultValue, @NotNull Function<SELF, C> createNewInstance, @Nullable Consumer<List<T>> saveConsumer, Text resetButtonKey, boolean requiresRestart) {
         this(fieldName, tooltipSupplier, defaultValue, createNewInstance, saveConsumer, resetButtonKey, requiresRestart, true, true);
     }
     
     @ApiStatus.Internal
-    @Deprecated
     public BaseListEntry(@NotNull Text fieldName, @Nullable Supplier<Optional<Text[]>> tooltipSupplier, @NotNull Supplier<List<T>> defaultValue, @NotNull Function<SELF, C> createNewInstance, @Nullable Consumer<List<T>> saveConsumer, Text resetButtonKey, boolean requiresRestart, boolean deleteButtonEnabled, boolean insertInFront) {
         super(fieldName, tooltipSupplier, requiresRestart);
         this.deleteButtonEnabled = deleteButtonEnabled;
@@ -93,6 +91,16 @@ public abstract class BaseListEntry<T, C extends BaseListCell, SELF extends Base
         this.defaultValue = defaultValue;
     }
     
+    @Override
+    public boolean isExpanded() {
+        return expanded;
+    }
+    
+    @Override
+    public void setExpanded(boolean expanded) {
+        this.expanded = expanded;
+    }
+    
     @Override
     public boolean isEdited() {
         if (super.isEdited()) return true;
@@ -262,6 +270,11 @@ public abstract class BaseListEntry<T, C extends BaseListCell, SELF extends Base
         }
     }
     
+    @Override
+    public int getInitialReferenceOffset() {
+        return 24;
+    }
+    
     public boolean insertInFront() {
         return insertInFront;
     }

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

@@ -1,7 +1,6 @@
 package me.shedaniel.clothconfig2.gui.entries;
 
 import me.shedaniel.clothconfig2.gui.widget.ColorDisplayWidget;
-import me.shedaniel.clothconfig2.mixin.ButtonWidgetHooks;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.widget.TextFieldWidget;
 import net.minecraft.client.util.math.MatrixStack;
@@ -35,9 +34,9 @@ public class ColorEntry extends TextFieldListEntry<Integer> {
         this.original = value;
         this.textFieldWidget.setText(getHexColorString(value));
         this.colorDisplayWidget = new ColorDisplayWidget(textFieldWidget, 0, 0, 20, getColorValueColor(textFieldWidget.getText()));
-        ((ButtonWidgetHooks) this.resetButton).setOnPress(button -> {
+        this.resetButton.onPress = button -> {
             this.textFieldWidget.setText(getHexColorString(defaultValue.get()));
-        });
+        };
     }
     
     @Override

+ 17 - 1
src/main/java/me/shedaniel/clothconfig2/gui/entries/MultiElementListEntry.java

@@ -3,6 +3,7 @@ package me.shedaniel.clothconfig2.gui.entries;
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
+import me.shedaniel.clothconfig2.api.Expandable;
 import me.shedaniel.clothconfig2.gui.widget.DynamicEntryListWidget;
 import me.shedaniel.math.Rectangle;
 import net.fabricmc.api.EnvType;
@@ -25,7 +26,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 @Environment(EnvType.CLIENT)
-public class MultiElementListEntry<T> extends TooltipListEntry<T> {
+public class MultiElementListEntry<T> extends TooltipListEntry<T> implements Expandable {
     
     private static final Identifier CONFIG_TEX = new Identifier("cloth-config2", "textures/gui/cloth_config.png");
     private final T object;
@@ -142,6 +143,11 @@ public class MultiElementListEntry<T> extends TooltipListEntry<T> {
         }
     }
     
+    @Override
+    public int getInitialReferenceOffset() {
+        return 24;
+    }
+    
     @Override
     public void lateRender(MatrixStack matrices, int mouseX, int mouseY, float delta) {
         if (expanded) {
@@ -187,6 +193,16 @@ public class MultiElementListEntry<T> extends TooltipListEntry<T> {
             return errors.stream().findFirst();
     }
     
+    @Override
+    public boolean isExpanded() {
+        return this.expanded;
+    }
+    
+    @Override
+    public void setExpanded(boolean expanded) {
+        this.expanded = expanded;
+    }
+    
     public class CategoryLabelWidget implements Element {
         private Rectangle rectangle = new Rectangle();
         

+ 10 - 1
src/main/java/me/shedaniel/clothconfig2/gui/entries/NestedListListEntry.java

@@ -3,6 +3,7 @@ package me.shedaniel.clothconfig2.gui.entries;
 import com.google.common.collect.Lists;
 import me.shedaniel.clothconfig2.api.AbstractConfigEntry;
 import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
+import me.shedaniel.clothconfig2.api.ReferenceProvider;
 import me.shedaniel.clothconfig2.gui.entries.NestedListListEntry.NestedListCell;
 import me.shedaniel.clothconfig2.gui.widget.DynamicEntryListWidget;
 import net.fabricmc.api.EnvType;
@@ -32,6 +33,9 @@ public final class NestedListListEntry<T, INNER extends AbstractConfigListEntry<
     @Deprecated
     public NestedListListEntry(Text fieldName, List<T> value, boolean defaultExpanded, Supplier<Optional<Text[]>> tooltipSupplier, Consumer<List<T>> saveConsumer, Supplier<List<T>> defaultValue, Text resetButtonKey, boolean deleteButtonEnabled, boolean insertInFront, BiFunction<T, NestedListListEntry<T, INNER>, INNER> createNewCell) {
         super(fieldName, value, defaultExpanded, null, null, defaultValue, resetButtonKey, false, deleteButtonEnabled, insertInFront, (t, nestedListListEntry) -> new NestedListCell<>(t, nestedListListEntry, createNewCell.apply(t, nestedListListEntry)));
+        for (NestedListCell<T, INNER> cell : cells) {
+            referencableEntries.add(cell.nestedEntry);
+        }
         setReferencableEntries(referencableEntries);
     }
     
@@ -44,7 +48,7 @@ public final class NestedListListEntry<T, INNER extends AbstractConfigListEntry<
      * @param <T> the configuration object type
      * @see NestedListListEntry
      */
-    public static class NestedListCell<T, INNER extends AbstractConfigListEntry<T>> extends AbstractListListEntry.AbstractListCell<T, NestedListCell<T, INNER>, NestedListListEntry<T, INNER>> {
+    public static class NestedListCell<T, INNER extends AbstractConfigListEntry<T>> extends AbstractListListEntry.AbstractListCell<T, NestedListCell<T, INNER>, NestedListListEntry<T, INNER>> implements ReferenceProvider<T> {
         private final INNER nestedEntry;
         
         @ApiStatus.Internal
@@ -53,6 +57,11 @@ public final class NestedListListEntry<T, INNER extends AbstractConfigListEntry<
             this.nestedEntry = nestedEntry;
         }
         
+        @Override
+        public AbstractConfigEntry<T> provideReferenceEntry() {
+            return nestedEntry;
+        }
+        
         @Override
         public T getValue() {
             return nestedEntry.getValue();

+ 17 - 1
src/main/java/me/shedaniel/clothconfig2/gui/entries/SubCategoryListEntry.java

@@ -3,6 +3,7 @@ package me.shedaniel.clothconfig2.gui.entries;
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
+import me.shedaniel.clothconfig2.api.Expandable;
 import me.shedaniel.clothconfig2.gui.widget.DynamicEntryListWidget;
 import me.shedaniel.math.Rectangle;
 import net.fabricmc.api.EnvType;
@@ -23,7 +24,7 @@ import java.util.List;
 import java.util.Optional;
 
 @Environment(EnvType.CLIENT)
-public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigListEntry>> {
+public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigListEntry>> implements Expandable {
     
     private static final Identifier CONFIG_TEX = new Identifier("cloth-config2", "textures/gui/cloth_config.png");
     private List<AbstractConfigListEntry> entries;
@@ -42,6 +43,16 @@ public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigLi
         this.setReferencableEntries((List) entries);
     }
     
+    @Override
+    public boolean isExpanded() {
+        return expanded;
+    }
+    
+    @Override
+    public void setExpanded(boolean expanded) {
+        this.expanded = expanded;
+    }
+    
     @Override
     public boolean isRequiresRestart() {
         for (AbstractConfigListEntry entry : entries)
@@ -156,6 +167,11 @@ public class SubCategoryListEntry extends TooltipListEntry<List<AbstractConfigLi
         return 24;
     }
     
+    @Override
+    public int getInitialReferenceOffset() {
+        return 24;
+    }
+    
     @Override
     public List<? extends Element> children() {
         return expanded ? children : Collections.singletonList(widget);

+ 12 - 11
src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicEntryListWidget.java

@@ -19,6 +19,7 @@ import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Matrix4f;
+import org.jetbrains.annotations.ApiStatus;
 
 import java.util.AbstractList;
 import java.util.ArrayList;
@@ -336,20 +337,20 @@ public abstract class DynamicEntryListWidget<E extends DynamicEntryListWidget.En
         return false;
     }
     
-    public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
-        if (super.mouseDragged(double_1, double_2, int_1, double_3, double_4)) {
+    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+        if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
             return true;
-        } else if (int_1 == 0 && this.scrolling) {
-            if (double_2 < (double) this.top) {
+        } else if (button == 0 && this.scrolling) {
+            if (mouseY < (double) this.top) {
                 this.capYPosition(0.0F);
-            } else if (double_2 > (double) this.bottom) {
+            } else if (mouseY > (double) this.bottom) {
                 this.capYPosition(this.getMaxScroll());
             } else {
                 double double_5 = Math.max(1, this.getMaxScroll());
                 int int_2 = this.bottom - this.top;
                 int int_3 = MathHelper.clamp((int) ((float) (int_2 * int_2) / (float) this.getMaxScrollPosition()), 32, int_2 - 8);
                 double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
-                this.capYPosition(this.getScroll() + double_4 * double_6);
+                this.capYPosition(this.getScroll() + deltaY * double_6);
             }
             
             return true;
@@ -460,7 +461,7 @@ public abstract class DynamicEntryListWidget<E extends DynamicEntryListWidget.En
     }
     
     @SuppressWarnings("deprecation")
-    protected void renderHoleBackground(MatrixStack matrices, int int_1, int int_2, int int_3, int int_4) {
+    protected void renderHoleBackground(MatrixStack matrices, int y1, int y2, int alpha1, int alpha2) {
         Tessellator tessellator = Tessellator.getInstance();
         BufferBuilder buffer = tessellator.getBuffer();
         this.client.getTextureManager().bindTexture(backgroundLocation);
@@ -468,10 +469,10 @@ public abstract class DynamicEntryListWidget<E extends DynamicEntryListWidget.En
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         float float_1 = 32.0F;
         buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
-        buffer.vertex(matrix, this.left, int_2, 0.0F).texture(0, ((float) int_2 / 32.0F)).color(64, 64, 64, int_4).next();
-        buffer.vertex(matrix, this.left + this.width, int_2, 0.0F).texture(((float) this.width / 32.0F), ((float) int_2 / 32.0F)).color(64, 64, 64, int_4).next();
-        buffer.vertex(matrix, this.left + this.width, int_1, 0.0F).texture(((float) this.width / 32.0F), ((float) int_1 / 32.0F)).color(64, 64, 64, int_3).next();
-        buffer.vertex(matrix, this.left, int_1, 0.0F).texture(0, ((float) int_1 / 32.0F)).color(64, 64, 64, int_3).next();
+        buffer.vertex(matrix, this.left, y2, 0.0F).texture(0, ((float) y2 / 32.0F)).color(64, 64, 64, alpha2).next();
+        buffer.vertex(matrix, this.left + this.width, y2, 0.0F).texture(((float) this.width / 32.0F), ((float) y2 / 32.0F)).color(64, 64, 64, alpha2).next();
+        buffer.vertex(matrix, this.left + this.width, y1, 0.0F).texture(((float) this.width / 32.0F), ((float) y1 / 32.0F)).color(64, 64, 64, alpha1).next();
+        buffer.vertex(matrix, this.left, y1, 0.0F).texture(0, ((float) y1 / 32.0F)).color(64, 64, 64, alpha1).next();
         tessellator.draw();
     }
     

+ 12 - 12
src/main/java/me/shedaniel/clothconfig2/gui/widget/DynamicNewSmoothScrollingEntryListWidget.java

@@ -72,22 +72,22 @@ public abstract class DynamicNewSmoothScrollingEntryListWidget<E extends Dynamic
     }
     
     @Override
-    public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
+    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
         if (!smoothScrolling)
-            return super.mouseDragged(double_1, double_2, int_1, double_3, double_4);
-        if ((this.getFocused() != null && this.isDragging() && int_1 == 0) && this.getFocused().mouseDragged(double_1, double_2, int_1, double_3, double_4)) {
+            return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+        if ((this.getFocused() != null && this.isDragging() && button == 0) && this.getFocused().mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
             return true;
-        } else if (int_1 == 0 && this.scrolling) {
-            if (double_2 < (double) this.top) {
+        } else if (button == 0 && this.scrolling) {
+            if (mouseY < (double) this.top) {
                 this.capYPosition(0.0D);
-            } else if (double_2 > (double) this.bottom) {
+            } else if (mouseY > (double) this.bottom) {
                 this.capYPosition(this.getMaxScroll());
             } else {
                 double double_5 = Math.max(1, this.getMaxScroll());
                 int int_2 = this.bottom - this.top;
                 int int_3 = MathHelper.clamp((int) ((float) (int_2 * int_2) / (float) this.getMaxScrollPosition()), 32, int_2 - 8);
                 double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
-                this.capYPosition(MathHelper.clamp(this.getScroll() + double_4 * double_6, 0, getMaxScroll()));
+                this.capYPosition(MathHelper.clamp(this.getScroll() + deltaY * double_6, 0, getMaxScroll()));
             }
             return true;
         }
@@ -95,18 +95,18 @@ public abstract class DynamicNewSmoothScrollingEntryListWidget<E extends Dynamic
     }
     
     @Override
-    public boolean mouseScrolled(double double_1, double double_2, double double_3) {
+    public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
         for (E entry : children()) {
-            if (entry.mouseScrolled(double_1, double_2, double_3)) {
+            if (entry.mouseScrolled(mouseX, mouseY, amount)) {
                 return true;
             }
         }
         if (!smoothScrolling) {
-            scroll += 16 * -double_3;
-            this.scroll = MathHelper.clamp(double_3, 0.0D, this.getMaxScroll());
+            scroll += 16 * -amount;
+            this.scroll = MathHelper.clamp(amount, 0.0D, this.getMaxScroll());
             return true;
         }
-        offset(ClothConfigInitializer.getScrollStep() * -double_3, true);
+        offset(ClothConfigInitializer.getScrollStep() * -amount, true);
         return true;
     }
     

+ 14 - 27
src/main/java/me/shedaniel/clothconfig2/impl/ConfigBuilderImpl.java

@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import me.shedaniel.clothconfig2.api.ConfigBuilder;
 import me.shedaniel.clothconfig2.api.ConfigCategory;
+import me.shedaniel.clothconfig2.api.Expandable;
 import me.shedaniel.clothconfig2.api.TabbedConfigScreen;
 import me.shedaniel.clothconfig2.gui.AbstractConfigScreen;
 import me.shedaniel.clothconfig2.gui.ClothConfigScreen;
@@ -29,6 +30,7 @@ public class ConfigBuilderImpl implements ConfigBuilder {
     private Screen parent;
     private Text title = new TranslatableText("text.cloth-config.config");
     private boolean globalized = false;
+    private boolean globalizedExpanded = true;
     private boolean editable = true;
     private boolean tabsSmoothScroll = true;
     private boolean listSmoothScroll = true;
@@ -51,6 +53,11 @@ public class ConfigBuilderImpl implements ConfigBuilder {
         this.globalized = globalized;
     }
     
+    @Override
+    public void setGlobalizedExpanded(boolean globalizedExpanded) {
+        this.globalizedExpanded = globalizedExpanded;
+    }
+    
     @Override
     public boolean isAlwaysShowTabs() {
         return alwaysShowTabs;
@@ -215,39 +222,19 @@ public class ConfigBuilderImpl implements ConfigBuilder {
             throw new NullPointerException("There cannot be no categories or fallback category!");
         AbstractConfigScreen screen;
         if (globalized) {
-            screen = new GlobalizedClothConfigScreen(parent, title, dataMap, defaultBackground) {
-                @Override
-                public void save() {
-                    if (savingRunnable != null)
-                        savingRunnable.run();
-                }
-        
-                @Override
-                protected void init() {
-                    super.init();
-                    afterInitConsumer.accept(this);
-                }
-            };
+            screen = new GlobalizedClothConfigScreen(parent, title, dataMap, defaultBackground);
         } else {
-            screen = new ClothConfigScreen(parent, title, dataMap, defaultBackground) {
-                @Override
-                public void save() {
-                    if (savingRunnable != null)
-                        savingRunnable.run();
-                }
-        
-                @Override
-                protected void init() {
-                    super.init();
-                    afterInitConsumer.accept(this);
-                }
-            };
-        };
+            screen = new ClothConfigScreen(parent, title, dataMap, defaultBackground);
+        }
+        screen.setSavingRunnable(savingRunnable);
         screen.setEditable(editable);
         screen.setFallbackCategory(fallbackCategory);
         screen.setTransparentBackground(transparentBackground);
         screen.setAlwaysShowTabs(alwaysShowTabs);
         screen.setConfirmSave(doesConfirmSave);
+        screen.setAfterInitConsumer(afterInitConsumer);
+        if (screen instanceof Expandable)
+            ((Expandable) screen).setExpanded(globalizedExpanded);
         if (screen instanceof TabbedConfigScreen)
             categoryBackground.forEach(((TabbedConfigScreen) screen)::registerCategoryBackground);
         return screen;

+ 0 - 13
src/main/java/me/shedaniel/clothconfig2/mixin/ButtonWidgetHooks.java

@@ -1,13 +0,0 @@
-package me.shedaniel.clothconfig2.mixin;
-
-import net.minecraft.client.gui.widget.ButtonWidget;
-import org.jetbrains.annotations.ApiStatus;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Accessor;
-
-@Mixin(ButtonWidget.class)
-@ApiStatus.Internal
-public interface ButtonWidgetHooks {
-    @Accessor("onPress")
-    void setOnPress(ButtonWidget.PressAction action);
-}

+ 4 - 0
src/main/resources/cloth-config2.accessWidener

@@ -0,0 +1,4 @@
+accessWidener   v1  named
+accessible method net/minecraft/client/font/TextRenderer drawLayer (Ljava/lang/String;FFIZLnet/minecraft/util/math/Matrix4f;Lnet/minecraft/client/render/VertexConsumerProvider;ZII)F
+accessible field net/minecraft/client/gui/widget/ButtonWidget onPress Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;
+mutable field net/minecraft/client/gui/widget/ButtonWidget onPress Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;

+ 2 - 4
src/main/resources/fabric.mod.json

@@ -3,7 +3,7 @@
   "id": "cloth-config2",
   "name": "Cloth Config v2",
   "description": "An API for config screens.",
-  "version": "${version}",
+  "version": "4.4.0",
   "authors": [
     "shedaniel"
   ],
@@ -19,12 +19,10 @@
       "me.shedaniel.clothconfig2.ClothConfigInitializer"
     ]
   },
-  "mixins": [
-    "mixin.cloth-config2.json"
-  ],
   "depends": {
     "fabricloader": ">=0.4.0"
   },
+  "accessWidener": "cloth-config2.accessWidener",
   "custom": {
     "modmenu:clientsideOnly": true
   }

+ 0 - 10
src/main/resources/mixin.cloth-config2.json

@@ -1,10 +0,0 @@
-{
-  "required": true,
-  "package": "me.shedaniel.clothconfig2.mixin",
-  "minVersion": "0.7.11",
-  "compatibilityLevel": "JAVA_8",
-  "client": ["ButtonWidgetHooks"],
-  "injectors": {
-    "defaultRequire": 1
-  }
-}