diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 84b216bc..462602cc 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -1,8 +1,7 @@ package org.embeddedt.modernfix.core.config; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.*; import com.google.gson.*; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -25,6 +24,7 @@ public class ModernFixEarlyConfig { private static final Logger LOGGER = LogManager.getLogger("ModernFixConfig"); private final Map options = new HashMap<>(); + private final Multimap optionsByCategory = HashMultimap.create(); public static final boolean OPTIFINE_PRESENT; @@ -171,12 +171,14 @@ public class ModernFixEarlyConfig { private ModernFixEarlyConfig(File file) { this.configFile = file; - + OptionCategories.load(); this.scanForAndBuildMixinOptions(); mixinOptions.addAll(DEFAULT_SETTING_OVERRIDES.keySet()); for(String optionName : mixinOptions) { boolean defaultEnabled = DEFAULT_SETTING_OVERRIDES.getOrDefault(optionName, true); - this.options.putIfAbsent(optionName, new Option(optionName, defaultEnabled, false)); + Option option = new Option(optionName, defaultEnabled, false); + this.options.putIfAbsent(optionName, option); + this.optionsByCategory.put(OptionCategories.getCategoryForOption(optionName), option); } // Defines the default rules which can be configured by the user or other mods. // You must manually add a rule for any new mixins not covered by an existing package rule. @@ -377,4 +379,8 @@ public class ModernFixEarlyConfig { public Map getOptionMap() { return Collections.unmodifiableMap(this.options); } + + public Multimap getOptionCategoryMap() { + return Multimaps.unmodifiableMultimap(this.optionsByCategory); + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/OptionCategories.java b/common/src/main/java/org/embeddedt/modernfix/core/config/OptionCategories.java new file mode 100644 index 00000000..b134e9d9 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/OptionCategories.java @@ -0,0 +1,55 @@ +package org.embeddedt.modernfix.core.config; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class OptionCategories { + private static String defaultCategory = "default"; + private static final Map categoryByName = new HashMap<>(); + private static final List categoryOrder = new ArrayList<>(); + public static void load() { + try(InputStream stream = OptionCategories.class.getResourceAsStream("/modernfix/option_categories.json")) { + if(stream == null) + throw new FileNotFoundException("option_categories.json"); + JsonObject object = new JsonParser().parse(new JsonReader(new InputStreamReader(stream, StandardCharsets.UTF_8))).getAsJsonObject(); + defaultCategory = object.get("default").getAsString(); + JsonObject obj = object.get("categories").getAsJsonObject(); + for(Map.Entry category : obj.entrySet()) { + categoryOrder.add(category.getKey()); + for(JsonElement e : category.getValue().getAsJsonArray()) { + categoryByName.put(e.getAsString(), category.getKey()); + } + } + } catch(IOException | RuntimeException e) { + e.printStackTrace(); + categoryOrder.clear(); + categoryByName.clear(); + categoryOrder.add("default"); + } + } + + public static List getCategoriesInOrder() { + return Collections.unmodifiableList(categoryOrder); + } + + public static String getCategoryForOption(String optionName) { + String category = categoryByName.get(optionName); + if(category == null) { + int lastDotIdx = optionName.lastIndexOf('.'); + if(lastDotIdx > 0) { + category = getCategoryForOption(optionName.substring(0, lastDotIdx - 1)); + } else + category = defaultCategory; + } + return category; + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java b/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java index 56bbfd6b..ea0a592c 100644 --- a/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java +++ b/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java @@ -4,7 +4,9 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Style; import net.minecraft.network.chat.TranslatableComponent; +import org.jetbrains.annotations.Nullable; public class ModernFixConfigScreen extends Screen { private OptionList optionList; @@ -22,11 +24,16 @@ public class ModernFixConfigScreen extends Screen { this.optionList = new OptionList(this, this.minecraft); this.addWidget(this.optionList); this.doneButton = new Button(this.width / 2 - 100, this.height - 29, 200, 20, CommonComponents.GUI_DONE, (arg) -> { - this.minecraft.setScreen(lastScreen); + this.onClose(); }); this.addRenderableWidget(this.doneButton); } + @Override + public void onClose() { + this.minecraft.setScreen(lastScreen); + } + @Override public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) { this.renderBackground(poseStack); @@ -35,4 +42,9 @@ public class ModernFixConfigScreen extends Screen { this.doneButton.setMessage(madeChanges ? new TranslatableComponent("modernfix.config.done_restart") : CommonComponents.GUI_DONE); super.render(poseStack, mouseX, mouseY, partialTicks); } + + @Override + public void renderComponentHoverEffect(PoseStack matrixStack, @Nullable Style style, int mouseX, int mouseY) { + super.renderComponentHoverEffect(matrixStack, style, mouseX, mouseY); + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixOptionInfoScreen.java b/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixOptionInfoScreen.java new file mode 100644 index 00000000..d90b3483 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/screen/ModernFixOptionInfoScreen.java @@ -0,0 +1,51 @@ +package org.embeddedt.modernfix.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.util.FormattedCharSequence; + +public class ModernFixOptionInfoScreen extends Screen { + private final Screen lastScreen; + private final Component description; + + public ModernFixOptionInfoScreen(Screen lastScreen, String optionName) { + super(new TextComponent(optionName)); + + this.lastScreen = lastScreen; + this.description = new TranslatableComponent("modernfix.option." + optionName); + } + + @Override + protected void init() { + super.init(); + this.addRenderableWidget(new Button(this.width / 2 - 100, this.height - 29, 200, 20, CommonComponents.GUI_DONE, (button) -> { + this.onClose(); + })); + } + + @Override + public void onClose() { + this.minecraft.setScreen(lastScreen); + } + + private void drawMultilineString(PoseStack mStack, Font fr, Component str, int x, int y) { + for(FormattedCharSequence s : fr.split(str, this.width - 50)) { + fr.drawShadow(mStack, s, (float)x, (float)y, 16777215); + y += fr.lineHeight; + } + } + + @Override + public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(poseStack); + drawCenteredString(poseStack, this.font, this.title, this.width / 2, 8, 16777215); + this.drawMultilineString(poseStack, this.minecraft.font, description, 10, 50); + super.render(poseStack, mouseX, mouseY, partialTicks); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/screen/OptionList.java b/common/src/main/java/org/embeddedt/modernfix/screen/OptionList.java index 03ee91d2..6c8a0905 100644 --- a/common/src/main/java/org/embeddedt/modernfix/screen/OptionList.java +++ b/common/src/main/java/org/embeddedt/modernfix/screen/OptionList.java @@ -1,25 +1,25 @@ package org.embeddedt.modernfix.screen; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.ContainerObjectSelectionList; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.TextComponent; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.*; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.core.config.Option; +import org.embeddedt.modernfix.core.config.OptionCategories; +import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class OptionList extends ContainerObjectSelectionList { @@ -28,8 +28,19 @@ public class OptionList extends ContainerObjectSelectionList { private static final Component OPTION_ON = new TranslatableComponent("modernfix.option.on").withStyle(ChatFormatting.GREEN); private static final Component OPTION_OFF = new TranslatableComponent("modernfix.option.off").withStyle(ChatFormatting.RED); + private static final Set OPTIONS_MISSING_HELP = new HashSet<>(); + private ModernFixConfigScreen mainScreen; + private static MutableComponent getOptionComponent(String optionName) { + String friendlyKey = "modernfix.option.name." + optionName; + TextComponent baseComponent = new TextComponent(optionName); + if(I18n.exists(friendlyKey)) + return new TranslatableComponent(friendlyKey).withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, baseComponent))); + else + return baseComponent; + } + public OptionList(ModernFixConfigScreen arg, Minecraft arg2) { super(arg2,arg.width + 45, arg.height, 43, arg.height - 32, 20); @@ -37,13 +48,26 @@ public class OptionList extends ContainerObjectSelectionList { this.mainScreen = arg; int maxW = 0; - Map optionMap = ModernFixMixinPlugin.instance.config.getOptionMap(); - List sortedKeys = optionMap.keySet().stream().filter(key -> !key.equals("mixin.core")).sorted().collect(Collectors.toList()); - for(String key : sortedKeys) { - Option option = optionMap.get(key); - int w = this.minecraft.font.width(key); - maxW = Math.max(w, maxW); - this.addEntry(new OptionEntry(key, option)); + Multimap optionsByCategory = ModernFixMixinPlugin.instance.config.getOptionCategoryMap(); + List theCategories = OptionCategories.getCategoriesInOrder(); + for(String category : theCategories) { + String categoryTranslationKey = "modernfix.option.category." + category; + this.addEntry(new CategoryEntry(new TranslatableComponent(categoryTranslationKey) + .withStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableComponent(categoryTranslationKey + ".description")))) + )); + List