From 05069aa62b09f02a8cd6e526ec58a30347a56500 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 27 Jul 2022 23:25:27 +0800 Subject: WIP Module --- .../rei/impl/client/config/ConfigManagerImpl.java | 494 ++++++++++++++++ .../rei/impl/client/config/ConfigObjectImpl.java | 658 +++++++++++++++++++++ .../config/addon/ConfigAddonRegistryImpl.java | 61 ++ .../client/config/addon/ConfigAddonsScreen.java | 192 ++++++ .../client/config/entries/ConfigAddonsEntry.java | 96 +++ .../config/entries/RecipeScreenTypeEntry.java | 115 ++++ .../SearchFilterSyntaxHighlightingEntry.java | 117 ++++ .../impl/client/config/entries/TitleTextEntry.java | 87 +++ 8 files changed, 1820 insertions(+) create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonRegistryImpl.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigAddonsEntry.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/entries/RecipeScreenTypeEntry.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/entries/SearchFilterSyntaxHighlightingEntry.java create mode 100644 runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/entries/TitleTextEntry.java (limited to 'runtime-engine/configs/src/main/java') diff --git a/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java new file mode 100644 index 000000000..2970c5b62 --- /dev/null +++ b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java @@ -0,0 +1,494 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.config; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Pair; +import dev.architectury.hooks.client.screen.ScreenHooks; +import me.shedaniel.autoconfig.AutoConfig; +import me.shedaniel.autoconfig.annotation.ConfigEntry; +import me.shedaniel.autoconfig.gui.ConfigScreenProvider; +import me.shedaniel.autoconfig.gui.registry.GuiRegistry; +import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; +import me.shedaniel.autoconfig.util.Utils; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Jankson; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.JsonNull; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.JsonObject; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.JsonPrimitive; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.api.DeserializationException; +import me.shedaniel.clothconfig2.api.*; +import me.shedaniel.clothconfig2.gui.AbstractConfigScreen; +import me.shedaniel.clothconfig2.gui.GlobalizedClothConfigScreen; +import me.shedaniel.clothconfig2.gui.entries.KeyCodeEntry; +import me.shedaniel.clothconfig2.gui.entries.TextListEntry; +import me.shedaniel.rei.api.client.REIRuntime; +import me.shedaniel.rei.api.client.config.ConfigManager; +import me.shedaniel.rei.api.client.config.addon.ConfigAddonRegistry; +import me.shedaniel.rei.api.client.config.entry.EntryStackProvider; +import me.shedaniel.rei.api.client.favorites.FavoriteEntry; +import me.shedaniel.rei.api.client.gui.config.CheatingMode; +import me.shedaniel.rei.api.client.gui.config.DisplayScreenType; +import me.shedaniel.rei.api.client.gui.config.SyntaxHighlightingMode; +import me.shedaniel.rei.api.client.overlay.ScreenOverlay; +import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.api.common.util.ImmutableTextComponent; +import me.shedaniel.rei.impl.client.REIRuntimeImpl; +import me.shedaniel.rei.impl.client.config.addon.ConfigAddonRegistryImpl; +import me.shedaniel.rei.impl.client.config.entries.*; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringRule; +import me.shedaniel.rei.impl.client.entry.filtering.rules.ManualFilteringRule; +import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; +import me.shedaniel.rei.impl.client.gui.credits.CreditsScreen; +import me.shedaniel.rei.impl.client.gui.performance.entry.PerformanceEntry; +import me.shedaniel.rei.impl.common.InternalLogger; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.chat.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionResult; +import org.jetbrains.annotations.ApiStatus; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static me.shedaniel.autoconfig.util.Utils.getUnsafely; +import static me.shedaniel.autoconfig.util.Utils.setUnsafely; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public class ConfigManagerImpl implements ConfigManagerInternal { + private boolean craftableOnly = false; + private final Gson gson = new GsonBuilder().create(); + private ConfigObjectImpl object; + + public ConfigManagerImpl() { + AutoConfig.register(ConfigObjectImpl.class, (definition, configClass) -> new JanksonConfigSerializer<>(definition, configClass, buildJankson(Jankson.builder()))); + GuiRegistry guiRegistry = AutoConfig.getGuiRegistry(ConfigObjectImpl.class); + guiRegistry.registerPredicateProvider((i13n, field, config, defaults, guiProvider) -> { + if (field.isAnnotationPresent(ConfigEntry.Gui.Excluded.class)) + return Collections.emptyList(); + KeyCodeEntry entry = ConfigEntryBuilder.create().startModifierKeyCodeField(new TranslatableComponent(i13n), getUnsafely(field, config, ModifierKeyCode.unknown())).setModifierDefaultValue(() -> getUnsafely(field, defaults)).setModifierSaveConsumer(newValue -> setUnsafely(field, config, newValue)).build(); + return Collections.singletonList(entry); + }, field -> field.getType() == ModifierKeyCode.class); + guiRegistry.registerAnnotationProvider((i13n, field, config, defaults, guiProvider) -> { + ConfigObjectImpl.UsePercentage bounds = field.getAnnotation(ConfigObjectImpl.UsePercentage.class); + return Collections.singletonList(ConfigEntryBuilder.create().startIntSlider(new TranslatableComponent(i13n), Mth.ceil(Utils.getUnsafely(field, config, 0.0) * 100), Mth.ceil(bounds.min() * 100), Mth.ceil(bounds.max() * 100)).setDefaultValue(() -> Mth.ceil((double) Utils.getUnsafely(field, defaults) * 100)).setSaveConsumer((newValue) -> { + setUnsafely(field, config, newValue / 100d); + }).setTextGetter(integer -> new TextComponent(bounds.prefix() + String.format("%d%%", integer))).build()); + }, (field) -> field.getType() == Double.TYPE || field.getType() == Double.class, ConfigObjectImpl.UsePercentage.class); + + guiRegistry.registerAnnotationProvider((i13n, field, config, defaults, guiProvider) -> + Collections.singletonList(new RecipeScreenTypeEntry(220, new TranslatableComponent(i13n), getUnsafely(field, config, DisplayScreenType.UNSET), getUnsafely(field, defaults), type -> setUnsafely(field, config, type))) + , (field) -> field.getType() == DisplayScreenType.class, ConfigObjectImpl.UseSpecialRecipeTypeScreen.class); + guiRegistry.registerAnnotationProvider((i13n, field, config, defaults, guiProvider) -> + Collections.singletonList(new SearchFilterSyntaxHighlightingEntry(new TranslatableComponent(i13n), getUnsafely(field, config, SyntaxHighlightingMode.COLORFUL), getUnsafely(field, defaults), type -> setUnsafely(field, config, type))) + , (field) -> field.getType() == SyntaxHighlightingMode.class, ConfigObjectImpl.UseSpecialSearchFilterSyntaxHighlightingScreen.class); + guiRegistry.registerAnnotationProvider((i13n, field, config, defaults, guiProvider) -> { + List> value = CollectionUtils.map(Utils.>>getUnsafely(field, config, new ArrayList<>()), EntryStackProvider::provide); + List> defaultValue = CollectionUtils.map(Utils.>>getUnsafely(field, defaults), EntryStackProvider::provide); + Consumer>> saveConsumer = (newValue) -> { + setUnsafely(field, config, CollectionUtils.map(newValue, EntryStackProvider::ofStack)); + }; + return REIRuntime.getInstance().getPreviousContainerScreen() == null || Minecraft.getInstance().getConnection() == null || Minecraft.getInstance().getConnection().getRecipeManager() == null ? + Collections.singletonList(new NoFilteringEntry(220, value, defaultValue, saveConsumer)) + : + Collections.singletonList(new FilteringEntry(220, value, ((ConfigObjectImpl.Advanced.Filtering) config).filteringRules, defaultValue, saveConsumer, list -> ((ConfigObjectImpl.Advanced.Filtering) config).filteringRules = Lists.newArrayList(list))); + } + , (field) -> field.getType() == List.class, ConfigObjectImpl.UseFilteringScreen.class); + InternalLogger.getInstance().info("Config loaded"); + saveConfig(); + } + + private static Jankson buildJankson(Jankson.Builder builder) { + // ResourceLocation + builder.registerSerializer(ResourceLocation.class, (location, marshaller) -> { + return new JsonPrimitive(location == null ? null : location.toString()); + }); + builder.registerDeserializer(String.class, ResourceLocation.class, (value, marshaller) -> { + return value == null ? null : new ResourceLocation(value); + }); + + // CheatingMode + builder.registerSerializer(CheatingMode.class, (value, marshaller) -> { + if (value == CheatingMode.WHEN_CREATIVE) { + return new JsonPrimitive("WHEN_CREATIVE"); + } else { + return new JsonPrimitive(value == CheatingMode.ON); + } + }); + builder.registerDeserializer(Boolean.class, CheatingMode.class, (value, unmarshaller) -> { + return value ? CheatingMode.ON : CheatingMode.OFF; + }); + builder.registerDeserializer(String.class, CheatingMode.class, (value, unmarshaller) -> { + return CheatingMode.valueOf(value.toUpperCase(Locale.ROOT)); + }); + + // InputConstants.Key + builder.registerSerializer(InputConstants.Key.class, (value, marshaller) -> { + return new JsonPrimitive(value.getName()); + }); + builder.registerDeserializer(String.class, InputConstants.Key.class, (value, marshaller) -> { + return InputConstants.getKey(value); + }); + + // ModifierKeyCode + builder.registerSerializer(ModifierKeyCode.class, (value, marshaller) -> { + JsonObject object = new JsonObject(); + object.put("keyCode", new JsonPrimitive(value.getKeyCode().getName())); + object.put("modifier", new JsonPrimitive(value.getModifier().getValue())); + return object; + }); + builder.registerDeserializer(JsonObject.class, ModifierKeyCode.class, (value, marshaller) -> { + String code = value.get(String.class, "keyCode"); + if (code.endsWith(".unknown")) { + return ModifierKeyCode.unknown(); + } else { + InputConstants.Key keyCode = InputConstants.getKey(code); + Modifier modifier = Modifier.of(value.getShort("modifier", (short) 0)); + return ModifierKeyCode.of(keyCode, modifier); + } + }); + + // Tag + builder.registerSerializer(Tag.class, (value, marshaller) -> { + return marshaller.serialize(value.toString()); + }); + builder.registerDeserializer(String.class, Tag.class, (value, marshaller) -> { + try { + return TagParser.parseTag(value); + } catch (CommandSyntaxException e) { + throw new DeserializationException(e); + } + }); + + // CompoundTag + builder.registerSerializer(CompoundTag.class, (value, marshaller) -> { + return marshaller.serialize(value.toString()); + }); + builder.registerDeserializer(String.class, CompoundTag.class, (value, marshaller) -> { + try { + return TagParser.parseTag(value); + } catch (CommandSyntaxException e) { + throw new DeserializationException(e); + } + }); + + // EntryStackProvider + builder.registerSerializer(EntryStackProvider.class, (stack, marshaller) -> { + try { + return marshaller.serialize(stack.save()); + } catch (Exception e) { + e.printStackTrace(); + return JsonNull.INSTANCE; + } + }); + builder.registerDeserializer(Tag.class, EntryStackProvider.class, (value, marshaller) -> { + return EntryStackProvider.defer((CompoundTag) value); + }); + builder.registerDeserializer(String.class, EntryStackProvider.class, (value, marshaller) -> { + try { + return EntryStackProvider.defer(TagParser.parseTag(value)); + } catch (CommandSyntaxException e) { + e.printStackTrace(); + return EntryStackProvider.ofStack(EntryStack.empty()); + } + }); + + // FilteringRule + builder.registerSerializer(FilteringRule.class, (value, marshaller) -> { + try { + return marshaller.serialize(FilteringRule.save(value, new CompoundTag())); + } catch (Exception e) { + e.printStackTrace(); + return JsonNull.INSTANCE; + } + }); + builder.registerDeserializer(Tag.class, FilteringRule.class, (value, marshaller) -> { + try { + return FilteringRule.read((CompoundTag) value); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }); + builder.registerDeserializer(String.class, FilteringRule.class, (value, marshaller) -> { + try { + return FilteringRule.read(TagParser.parseTag(value)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }); + + // FavoriteEntry + builder.registerSerializer(FavoriteEntry.class, (value, marshaller) -> { + try { + return marshaller.serialize(value.save(new CompoundTag())); + } catch (Exception e) { + e.printStackTrace(); + return JsonNull.INSTANCE; + } + }); + builder.registerDeserializer(Tag.class, FavoriteEntry.class, (value, marshaller) -> { + return FavoriteEntry.readDelegated((CompoundTag) value); + }); + builder.registerDeserializer(String.class, FavoriteEntry.class, (value, marshaller) -> { + try { + CompoundTag tag = TagParser.parseTag(value); + return FavoriteEntry.readDelegated(tag); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }); + + return builder.build(); + } + + @Override + public void startReload() { + } + + public static ConfigManagerImpl getInstance() { + return (ConfigManagerImpl) ConfigManager.getInstance(); + } + + @Override + public void saveConfig() { + if (getConfig().getFilteringRules().stream().noneMatch(filteringRule -> filteringRule instanceof ManualFilteringRule)) { + getConfig().getFilteringRules().add(new ManualFilteringRule()); + } + AutoConfig.getConfigHolder(ConfigObjectImpl.class).registerLoadListener((configHolder, configObject) -> { + object = configObject; + return InteractionResult.PASS; + }); + AutoConfig.getConfigHolder(ConfigObjectImpl.class).save(); + InternalLogger.getInstance().debug("Config saved"); + } + + @Override + public ConfigObjectImpl getConfig() { + if (object == null) { + object = AutoConfig.getConfigHolder(ConfigObjectImpl.class).getConfig(); + } + return object; + } + + @Override + public boolean isCraftableOnlyEnabled() { + return craftableOnly && getConfig().isCraftableFilterEnabled(); + } + + @Override + public void toggleCraftableOnly() { + craftableOnly = !craftableOnly; + } + + @SuppressWarnings("deprecation") + @Override + public Screen getConfigScreen(Screen parent) { + class EmptyEntry extends AbstractConfigListEntry { + private final int height; + + public EmptyEntry(int height) { + super(new TextComponent(UUID.randomUUID().toString()), false); + this.height = height; + } + + public int getItemHeight() { + return this.height; + } + + public Object getValue() { + return null; + } + + public Optional getDefaultValue() { + return Optional.empty(); + } + + public boolean isMouseInside(int mouseX, int mouseY, int x, int y, int entryWidth, int entryHeight) { + return false; + } + + public void save() { + } + + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + } + + public List children() { + return Collections.emptyList(); + } + + public List narratables() { + return Collections.emptyList(); + } + } + + try { + ConfigScreenProvider provider = (ConfigScreenProvider) AutoConfig.getConfigScreen(ConfigObjectImpl.class, parent); + provider.setI13nFunction(manager -> "config.roughlyenoughitems"); + provider.setOptionFunction((baseI13n, field) -> field.isAnnotationPresent(ConfigObjectImpl.DontApplyFieldName.class) ? baseI13n : String.format("%s.%s", baseI13n, field.getName())); + provider.setCategoryFunction((baseI13n, categoryName) -> String.format("%s.%s", baseI13n, categoryName)); + provider.setBuildFunction(builder -> { + builder.setGlobalized(true); + builder.setGlobalizedExpanded(false); + if (Minecraft.getInstance().getConnection() != null && Minecraft.getInstance().getConnection().getRecipeManager() != null) { + TextListEntry feedbackEntry = ConfigEntryBuilder.create().startTextDescription( + new TranslatableComponent("text.rei.feedback", new TranslatableComponent("text.rei.feedback.link") + .withStyle(style -> style + .withColor(TextColor.fromRgb(0xff1fc3ff)) + .withUnderlined(true) + .withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://forms.gle/5tdnK5WN1wng78pV8")) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ImmutableTextComponent("https://forms.gle/5tdnK5WN1wng78pV8"))) + )) + .withStyle(ChatFormatting.GRAY) + ).build(); + builder.getOrCreateCategory(new TranslatableComponent("config.roughlyenoughitems.advanced")).getEntries().add(0, feedbackEntry); + builder.getOrCreateCategory(new TranslatableComponent("config.roughlyenoughitems.advanced")).getEntries().add(0, new ReloadPluginsEntry(220)); + builder.getOrCreateCategory(new TranslatableComponent("config.roughlyenoughitems.advanced")).getEntries().add(0, new PerformanceEntry(220)); + } + return builder.setAfterInitConsumer(screen -> { + ConfigAddonRegistryImpl addonRegistry = (ConfigAddonRegistryImpl) ConfigAddonRegistry.getInstance(); + if (!addonRegistry.getAddons().isEmpty()) { + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new EmptyEntry(4)); + ConfigAddonsEntry configAddonsEntry = new ConfigAddonsEntry(220); + configAddonsEntry.setScreen((AbstractConfigScreen) screen); + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) configAddonsEntry); + } + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new EmptyEntry(4)); + TextListEntry supportText = ConfigEntryBuilder.create().startTextDescription( + new TranslatableComponent("text.rei.support.me.desc", + new TranslatableComponent("text.rei.support.me.patreon") + .withStyle(style -> style + .withColor(TextColor.fromRgb(0xff1fc3ff)) + .withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://patreon.com/shedaniel")) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ImmutableTextComponent("https://patreon.com/shedaniel"))) + ), + new TranslatableComponent("text.rei.support.me.bisect") + .withStyle(style -> style + .withColor(TextColor.fromRgb(0xff1fc3ff)) + .withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.bisecthosting.com/shedaniel")) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ImmutableTextComponent("https://www.bisecthosting.com/shedaniel"))) + ) + ) + .withStyle(ChatFormatting.GRAY) + ).build(); + supportText.setScreen((AbstractConfigScreen) screen); + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) supportText); + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new TitleTextEntry(new TranslatableComponent("text.rei.support.me"))); + ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new EmptyEntry(4)); + ScreenHooks.addRenderableWidget(screen, new Button(screen.width - 104, 4, 100, 20, new TranslatableComponent("text.rei.credits"), button -> { + CreditsScreen creditsScreen = new CreditsScreen(screen); + Minecraft.getInstance().setScreen(creditsScreen); + })); + }).setSavingRunnable(() -> { + saveConfig(); + EntryRegistry.getInstance().refilter(); + REIRuntime.getInstance().getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay); + if (REIRuntimeImpl.getSearchField() != null) { + ScreenOverlayImpl.getEntryListWidget().updateSearch(REIRuntimeImpl.getSearchField().getText(), true); + } + }).build(); + }); + return provider.get(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Object get(String fieldKey) { + Pair, Field> pair = locateField(fieldKey); + try { + return pair.getSecond().get(pair.getFirst().get()); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean set(String fieldKey, Object value) { + try { + Pair, Field> pair = locateField(fieldKey); + pair.getSecond().set(pair.getFirst().get(), value); + ConfigManager.getInstance().saveConfig(); + REIRuntime.getInstance().getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay); + return true; + } catch (Throwable e) { + return false; + } + } + + private static final Map, Field>> FIELD_CACHE = new HashMap<>(); + + private Pair, Field> locateField(String fieldKey) { + return FIELD_CACHE.computeIfAbsent(fieldKey, key -> { + // resolve dot separated field names + String[] fieldNames = key.split("\\."); + try { + Supplier owner = this::getConfig; + Field field = ConfigObjectImpl.class.getDeclaredField(fieldNames[0]); + field.setAccessible(true); + for (int i = 1; i < fieldNames.length; i++) { + Supplier previousOwner = owner; + Field previousField = field; + owner = () -> { + try { + return previousField.get(previousOwner.get()); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }; + field = field.getType().getDeclaredField(fieldNames[i]); + field.setAccessible(true); + } + return new Pair<>(owner, field); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java new file mode 100644 index 000000000..d0b63a1ff --- /dev/null +++ b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java @@ -0,0 +1,658 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.config; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.architectury.platform.Platform; +import me.shedaniel.autoconfig.ConfigData; +import me.shedaniel.autoconfig.annotation.Config; +import me.shedaniel.autoconfig.annotation.ConfigEntry; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; +import me.shedaniel.clothconfig2.api.Modifier; +import me.shedaniel.clothconfig2.api.ModifierKeyCode; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.config.entry.EntryStackProvider; +import me.shedaniel.rei.api.client.favorites.FavoriteEntry; +import me.shedaniel.rei.api.client.gui.config.*; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringRule; +import me.shedaniel.rei.impl.client.gui.widget.favorites.FavoritesEntriesManager; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.level.GameType; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +@ApiStatus.Internal +@Config(name = "roughlyenoughitems/config") +@Environment(EnvType.CLIENT) +public class ConfigObjectImpl implements ConfigObject, ConfigData { + @ConfigEntry.Category("basics") @ConfigEntry.Gui.TransitiveObject @DontApplyFieldName + public Basics basics = new Basics(); + @ConfigEntry.Category("appearance") @ConfigEntry.Gui.TransitiveObject @DontApplyFieldName + private Appearance appearance = new Appearance(); + @ConfigEntry.Category("functionality") @ConfigEntry.Gui.TransitiveObject @DontApplyFieldName + private Functionality functionality = new Functionality(); + @ConfigEntry.Category("advanced") @ConfigEntry.Gui.TransitiveObject @DontApplyFieldName + public Advanced advanced = new Advanced(); + + @Override + public boolean isOverlayVisible() { + return basics.overlayVisible; + } + + @Override + public void setOverlayVisible(boolean overlayVisible) { + basics.overlayVisible = overlayVisible; + } + + @Override + public boolean isCheating() { + return basics.cheating == CheatingMode.ON || (basics.cheating == CheatingMode.WHEN_CREATIVE && Minecraft.getInstance().gameMode != null + && Minecraft.getInstance().gameMode.getPlayerMode() == GameType.CREATIVE); + } + + @Override + public void setCheating(boolean cheating) { + basics.cheating = cheating ? CheatingMode.ON : CheatingMode.OFF; + } + + @Override + public CheatingMode getCheatingMode() { + return basics.cheating; + } + + @Override + public EntryPanelOrdering getItemListOrdering() { + return advanced.layout.entryPanelOrdering.getOrdering(); + } + + @Override + public boolean isItemListAscending() { + return advanced.layout.entryPanelOrdering.isAscending(); + } + + @Override + public boolean isUsingDarkTheme() { + return appearance.theme == AppearanceTheme.DARK; + } + + @Override + public boolean isGrabbingItems() { + return basics.cheatingStyle == ItemCheatingStyle.GRAB; + } + + @Override + public boolean isFavoritesAnimated() { + return basics.motion.favoritesAnimation; + } + + @Override + public boolean isToastDisplayedOnCopyIdentifier() { + return advanced.accessibility.toastDisplayedOnCopyIdentifier; + } + + @Override + public boolean isEntryListWidgetScrolled() { + return appearance.scrollingEntryListWidget; + } + + @Override + public boolean shouldAppendModNames() { + return advanced.tooltips.appendModNames; + } + + @Override + public DisplayScreenType getRecipeScreenType() { + return appearance.recipeScreenType; + } + + @Override + public void setRecipeScreenType(DisplayScreenType displayScreenType) { + appearance.recipeScreenType = displayScreenType; + } + + @Override + public SearchFieldLocation getSearchFieldLocation() { + return appearance.layout.searchFieldLocation; + } + + @Override + public DisplayPanelLocation getDisplayPanelLocation() { + return advanced.accessibility.displayPanelLocation; + } + + @Override + public boolean isCraftableFilterEnabled() { + return appearance.layout.showCraftableOnlyButton; + } + + @Override + public String getGamemodeCommand() { + return advanced.commands.gamemodeCommand; + } + + @Override + public String getGiveCommand() { + return advanced.commands.giveCommand; + } + + @Override + public String getWeatherCommand() { + return advanced.commands.weatherCommand; + } + + @Override + public String getTimeCommand() { + return advanced.commands.timeCommand; + } + + @Override + public int getMaxRecipePerPage() { + return advanced.layout.maxRecipesPerPage; + } + + @Override + public int getMaxRecipesPageHeight() { + return advanced.layout.maxRecipesPageHeight; + } + + @Override + @Nullable + public ResourceLocation getInputMethodId() { + return functionality.inputMethod; + } + + @Override + public boolean doesDisableRecipeBook() { + return functionality.disableRecipeBook; + } + + @Override + public boolean doesFixTabCloseContainer() { + return functionality.disableRecipeBook; + } + + @Override + public boolean isLeftSideMobEffects() { + return functionality.leftSideMobEffects; + } + + @Override + public boolean areClickableRecipeArrowsEnabled() { + return advanced.miscellaneous.clickableRecipeArrows; + } + + @Override + public RecipeBorderType getRecipeBorderType() { + return appearance.recipeBorder; + } + + @Override + public boolean isCompositeScrollBarPermanent() { + return advanced.accessibility.compositeScrollBarPermanent; + } + + @Override + public boolean doesRegisterRecipesInAnotherThread() { + return advanced.miscellaneous.registerRecipesInAnotherThread; + } + + @Override + public boolean doesSnapToRows() { + return false; + } + + @Override + public boolean isFavoritesEnabled() { + return basics.favoritesEnabled; + } + + @Override + public boolean doDisplayFavoritesTooltip() { + return isFavoritesEnabled() && advanced.tooltips.displayFavoritesTooltip; + } + + @Override + public boolean doesFastEntryRendering() { + return advanced.miscellaneous.newFastEntryRendering; + } + + @Override + public boolean doesCacheEntryRendering() { + return advanced.miscellaneous.cachingFastEntryRendering; + } + + @Override + public boolean doDebugRenderTimeRequired() { + return advanced.layout.debugRenderTimeRequired; + } + + @Override + public boolean doMergeDisplayUnderOne() { + return advanced.layout.mergeDisplayUnderOne; + } + + @Override + public FavoriteAddWidgetMode getFavoriteAddWidgetMode() { + return advanced.layout.favoriteAddWidgetMode; + } + + @Override + public ModifierKeyCode getFavoriteKeyCode() { + return basics.keyBindings.favoriteKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.favoriteKeybind; + } + + @Override + public ModifierKeyCode getRecipeKeybind() { + return basics.keyBindings.recipeKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.recipeKeybind; + } + + @Override + public ModifierKeyCode getUsageKeybind() { + return basics.keyBindings.usageKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.usageKeybind; + } + + @Override + public ModifierKeyCode getHideKeybind() { + return basics.keyBindings.hideKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.hideKeybind; + } + + @Override + public ModifierKeyCode getPreviousPageKeybind() { + return basics.keyBindings.previousPageKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.previousPageKeybind; + } + + @Override + public ModifierKeyCode getNextPageKeybind() { + return basics.keyBindings.nextPageKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.nextPageKeybind; + } + + @Override + public ModifierKeyCode getFocusSearchFieldKeybind() { + return basics.keyBindings.focusSearchFieldKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.focusSearchFieldKeybind; + } + + @Override + public ModifierKeyCode getCopyRecipeIdentifierKeybind() { + return basics.keyBindings.copyRecipeIdentifierKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.copyRecipeIdentifierKeybind; + } + + @Override + public ModifierKeyCode getExportImageKeybind() { + return basics.keyBindings.exportImageKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.exportImageKeybind; + } + + @Override + public ModifierKeyCode getPreviousScreenKeybind() { + return basics.keyBindings.previousScreenKeybind == null ? ModifierKeyCode.unknown() : basics.keyBindings.previousScreenKeybind; + } + + @Override + public double getEntrySize() { + return advanced.accessibility.entrySize; + } + + @Override + public boolean isUsingCompactTabs() { + return advanced.accessibility.useCompactTabs; + } + + @Override + public boolean isUsingCompactTabButtons() { + return advanced.accessibility.useCompactTabButtons; + } + + @Override + public boolean isLowerConfigButton() { + return appearance.layout.configButtonLocation == ConfigButtonPosition.LOWER; + } + + @Override + public List getFavoriteEntries() { + return FavoritesEntriesManager.INSTANCE.asListView(); + } + + public List getConfigFavoriteEntries() { + return basics.favorites; + } + + public List getHiddenFavoriteEntries() { + return basics.hiddenFavorites; + } + + public List getDisplayHistory() { + return basics.displayHistory; + } + + @Override + public List> getFilteredStackProviders() { + return advanced.filtering.filteredStacks; + } + + @ApiStatus.Experimental + @Override + public boolean shouldFilterDisplays() { + return advanced.filtering.shouldFilterDisplays; + } + + @ApiStatus.Internal + public List> getFilteringRules() { + return advanced.filtering.filteringRules; + } + + @Override + @ApiStatus.Experimental + public boolean shouldAsyncSearch() { + return advanced.search.asyncSearch; + } + + @Override + @ApiStatus.Experimental + public int getAsyncSearchPartitionSize() { + return advanced.search.asyncSearchPartitionSize; + } + + @Override + @ApiStatus.Experimental + public boolean doDebugSearchTimeRequired() { + return advanced.search.debugSearchTimeRequired; + } + + @Override + public boolean isSubsetsEnabled() { + return functionality.isSubsetsEnabled; + } + + @Override + public boolean isInventoryHighlightingAllowed() { + return functionality.allowInventoryHighlighting; + } + + @Override + public ItemCheatingMode getItemCheatingMode() { + return functionality.itemCheatingMode; + } + + @ApiStatus.Experimental + @Override + public double getHorizontalEntriesBoundariesPercentage() { + return Mth.clamp(appearance.horizontalEntriesBoundaries, 0.1, 1); + } + + @ApiStatus.Experimental + @Override + public double getVerticalEntriesBoundariesPercentage() { + return Mth.clamp(appearance.verticalEntriesBoundaries, 0.1, 1); + } + + @ApiStatus.Experimental + @Override + public double getHorizontalEntriesBoundariesColumns() { + return Mth.clamp(appearance.horizontalEntriesBoundariesColumns, 1, 1000); + } + + @ApiStatus.Experimental + @Override + public double getVerticalEntriesBoundariesRows() { + return Mth.clamp(appearance.verticalEntriesBoundariesRows, 1, 1000); + } + + @ApiStatus.Experimental + @Override + public double getFavoritesHorizontalEntriesBoundariesPercentage() { + return Mth.clamp(appearance.favoritesHorizontalEntriesBoundaries, 0.1, 1); + } + + @Override + public double getFavoritesHorizontalEntriesBoundariesColumns() { + return Mth.clamp(appearance.favoritesHorizontalEntriesBoundariesColumns, 1, 1000); + } + + @Override + public SyntaxHighlightingMode getSyntaxHighlightingMode() { + return appearance.syntaxHighlightingMode; + } + + @Override + public boolean isFocusModeZoomed() { + return appearance.isFocusModeZoomed; + } + + @Override + public SearchMode getTooltipSearchMode() { + return advanced.search.tooltipSearch; + } + + @Override + public SearchMode getTagSearchMode() { + return advanced.search.tagSearch; + } + + @Override + public SearchMode getIdentifierSearchMode() { + return advanced.search.identifierSearch; + } + + @Override + public SearchMode getModSearchMode() { + return advanced.search.modSearch; + } + + @Override + public boolean isJEICompatibilityLayerEnabled() { + return Platform.isForge() && advanced.enableJeiCompatibilityLayer; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface DontApplyFieldName {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface UseSpecialRecipeTypeScreen {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface UseSpecialSearchFilterSyntaxHighlightingScreen {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface UseFilteringScreen {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface UsePercentage { + double min(); + + double max(); + + String prefix() default "Size: "; + } + + public static class Basics { + @ConfigEntry.Gui.Excluded public List favorites = new ArrayList<>(); + @ConfigEntry.Gui.Excluded public List hiddenFavorites = new ArrayList<>(); + @ConfigEntry.Gui.Excluded public List displayHistory = new ArrayList<>(); + @Comment("Declares whether cheating mode is on.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private CheatingMode cheating = CheatingMode.OFF; + private boolean favoritesEnabled = true; + @ConfigEntry.Gui.CollapsibleObject(startExpanded = true) + private KeyBindings keyBindings = new KeyBindings(); + @Comment("Declares whether REI is visible.") @ConfigEntry.Gui.Excluded private boolean overlayVisible = true; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private ItemCheatingStyle cheatingStyle = ItemCheatingStyle.GRAB; + @ConfigEntry.Gui.CollapsibleObject + private Motion motion = new Motion(); + } + + public static class Motion { + private boolean favoritesAnimation = true; + } + + public static class KeyBindings { + private ModifierKeyCode recipeKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_R), Modifier.none()); + private ModifierKeyCode usageKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_U), Modifier.none()); + private ModifierKeyCode hideKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_O), Modifier.of(false, true, false)); + private ModifierKeyCode previousPageKeybind = ModifierKeyCode.unknown(); + private ModifierKeyCode nextPageKeybind = ModifierKeyCode.unknown(); + private ModifierKeyCode focusSearchFieldKeybind = ModifierKeyCode.unknown(); + private ModifierKeyCode copyRecipeIdentifierKeybind = ModifierKeyCode.of(InputConstants.Type.MOUSE.getOrCreate(InputConstants.MOUSE_BUTTON_MIDDLE), Modifier.none()); + private ModifierKeyCode favoriteKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_A), Modifier.none()); + private ModifierKeyCode exportImageKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_F8), Modifier.none()); + private ModifierKeyCode previousScreenKeybind = ModifierKeyCode.of(InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_BACKSPACE), Modifier.none()); + } + + public static class Appearance { + @UseSpecialRecipeTypeScreen private DisplayScreenType recipeScreenType = DisplayScreenType.UNSET; + @Comment("Declares the appearance of REI windows.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private AppearanceTheme theme = AppearanceTheme.LIGHT; + @ConfigEntry.Gui.CollapsibleObject(startExpanded = true) + private Layout layout = new Layout(); + @Comment("Declares the appearance of recipe's border.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private RecipeBorderType recipeBorder = RecipeBorderType.DEFAULT; + @Comment("Declares whether entry panel is scrolled.") private boolean scrollingEntryListWidget = false; + + public static class Layout { + @Comment("Declares the position of the search field.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchFieldLocation searchFieldLocation = SearchFieldLocation.CENTER; + @Comment("Declares the position of the config button.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private ConfigButtonPosition configButtonLocation = ConfigButtonPosition.LOWER; + @Comment("Declares whether the craftable filter button is enabled.") private boolean showCraftableOnlyButton = true; + } + + @UsePercentage(min = 0.1, max = 1.0, prefix = "Limit: ") private double horizontalEntriesBoundaries = 1.0; + @UsePercentage(min = 0.1, max = 1.0, prefix = "Limit: ") private double verticalEntriesBoundaries = 1.0; + private int horizontalEntriesBoundariesColumns = 50; + private int verticalEntriesBoundariesRows = 1000; + @UsePercentage(min = 0.1, max = 1.0, prefix = "Limit: ") private double favoritesHorizontalEntriesBoundaries = 1.0; + private int favoritesHorizontalEntriesBoundariesColumns = 50; + @UseSpecialSearchFilterSyntaxHighlightingScreen private SyntaxHighlightingMode syntaxHighlightingMode = SyntaxHighlightingMode.COLORFUL; + private boolean isFocusModeZoomed = false; + } + + public static class Functionality { + @ConfigEntry.Gui.Excluded @Nullable private ResourceLocation inputMethod = null; + @Comment("Declares whether REI should remove the recipe book.") private boolean disableRecipeBook = false; + @Comment("Declares whether mob effects should be on the left side instead of the right side.") private boolean leftSideMobEffects = false; + @Comment("Declares whether subsets is enabled.") private boolean isSubsetsEnabled = false; + private boolean allowInventoryHighlighting = true; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private ItemCheatingMode itemCheatingMode = ItemCheatingMode.REI_LIKE; + } + + public static class Advanced { + @ConfigEntry.Gui.CollapsibleObject + private Tooltips tooltips = new Tooltips(); + @ConfigEntry.Gui.CollapsibleObject + private Layout layout = new Layout(); + @ConfigEntry.Gui.CollapsibleObject + private Accessibility accessibility = new Accessibility(); + @ConfigEntry.Gui.CollapsibleObject + private Search search = new Search(); + @ConfigEntry.Gui.CollapsibleObject + private Commands commands = new Commands(); + @ConfigEntry.Gui.CollapsibleObject + private Miscellaneous miscellaneous = new Miscellaneous(); + @ConfigEntry.Gui.CollapsibleObject(startExpanded = true) + public Filtering filtering = new Filtering(); + @ConfigEntry.Gui.Excluded + public boolean enableJeiCompatibilityLayer = true; + + public static class Tooltips { + @Comment("Declares whether REI should append mod names to entries.") private boolean appendModNames = true; + @Comment("Declares whether favorites tooltip should be displayed.") private boolean displayFavoritesTooltip = false; + } + + public static class Layout { + @Comment("The ordering of the items on the entry panel.") + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private EntryPanelOrderingConfig entryPanelOrdering = EntryPanelOrderingConfig.REGISTRY_ASCENDING; + @Comment("Declares the maximum amount of recipes displayed in a page if possible.") @ConfigEntry.BoundedDiscrete(min = 2, max = 99) + private int maxRecipesPerPage = 8; + @Comment("Declares the maximum amount of recipes displayed in a page if possible.") @ConfigEntry.BoundedDiscrete(min = 100, max = 1000) + private int maxRecipesPageHeight = 300; + @Comment("Declares whether entry rendering time should be debugged.") private boolean debugRenderTimeRequired = false; + @Comment("Merges displays with equal contents under 1 display.") private boolean mergeDisplayUnderOne = true; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private FavoriteAddWidgetMode favoriteAddWidgetMode = FavoriteAddWidgetMode.ALWAYS_VISIBLE; + } + + public static class Accessibility { + @UsePercentage(min = 0.25, max = 4.0) private double entrySize = 1.0; + @Comment("Declares the position of the entry panel.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private DisplayPanelLocation displayPanelLocation = DisplayPanelLocation.RIGHT; + @Comment("Declares how the scrollbar in composite screen should act.") private boolean compositeScrollBarPermanent = false; + private boolean toastDisplayedOnCopyIdentifier = true; + @Comment("Declares whether REI should use compact tabs for categories.") private boolean useCompactTabs = true; + @Comment("Declares whether REI should use compact tab buttons for categories.") private boolean useCompactTabButtons = true; + } + + public static class Search { + @Comment("Declares whether search time should be debugged.") private boolean debugSearchTimeRequired = false; + @Comment("Declares whether REI should search async.") private boolean asyncSearch = true; + @Comment("Declares how many entries should be grouped one async search.") @ConfigEntry.BoundedDiscrete(min = 25, max = 400) + private int asyncSearchPartitionSize = 100; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchMode tooltipSearch = SearchMode.ALWAYS; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchMode tagSearch = SearchMode.PREFIX; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchMode identifierSearch = SearchMode.ALWAYS; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchMode modSearch = SearchMode.PREFIX; + } + + public static class Commands { + @Comment("Declares the command used to change gamemode.") private String gamemodeCommand = "/gamemode {gamemode}"; + @Comment("Declares the command used in servers to cheat items.") private String giveCommand = "/give {player_name} {item_identifier}{nbt} {count}"; + @Comment("Declares the command used to change weather.") private String weatherCommand = "/weather {weather}"; + @Comment("Declares the command used to change time.") private String timeCommand = "/time set {time}"; + } + + public static class Miscellaneous { + @Comment("Declares whether arrows in containers should be clickable.") private boolean clickableRecipeArrows = true; + private boolean registerRecipesInAnotherThread = true; + private boolean newFastEntryRendering = true; + @ConfigEntry.Gui.PrefixText + private boolean cachingFastEntryRendering = false; + } + + public static class Filtering { + @UseFilteringScreen private List> filteredStacks = new ArrayList<>(); + public boolean shouldFilterDisplays = true; + @ConfigEntry.Gui.Excluded public List> filteringRules = new ArrayList<>(); + } + } +} diff --git a/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonRegistryImpl.java b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonRegistryImpl.java new file mode 100644 index 000000000..6124cb83c --- /dev/null +++ b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonRegistryImpl.java @@ -0,0 +1,61 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.config.addon; + +import me.shedaniel.rei.api.client.config.addon.ConfigAddon; +import me.shedaniel.rei.api.client.config.addon.ConfigAddonRegistry; +import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.impl.common.InternalLogger; + +import java.util.ArrayList; +import java.util.List; + +public class ConfigAddonRegistryImpl implements ConfigAddonRegistry { + private final List addons = new ArrayList<>(); + + @Override + public void startReload() { + this.addons.clear(); + } + + @Override + public void endReload() { + InternalLogger.getInstance().debug("Registered %d config addons", this.addons.size()); + } + + @Override + public void register(ConfigAddon addon) { + this.addons.add(addon); + InternalLogger.getInstance().debug("Added config addon: %s [%s]", addon, addon.getName().getString()); + } + + @Override + public void acceptPlugin(REIClientPlugin plugin) { + plugin.registerConfigAddons(this); + } + + public List getAddons() { + return this.addons; + } +} diff --git a/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java new file mode 100644 index 000000000..9b0335d73 --- /dev/null +++ b/runtime-engine/configs/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java @@ -0,0 +1,192 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.config.addon; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; +import me.shedaniel.rei.api.client.config.addon.ConfigAddon; +import me.shedaniel.rei.api.client.config.addon.ConfigAddonRegistry; +import me.shedaniel.rei.impl.client.gui.InternalTextures; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class ConfigAddonsScreen extends Screen { + private AddonsList rulesList; + private final Screen parent; + + public ConfigAddonsScreen(Screen parent) { + super(new TranslatableComponent("text.rei.addons")); + this.parent = parent; + } + + @Override + public void init() { + super.init(); + { + Component backText = new TextComponent("↩ ").append(new TranslatableComponent("gui.back")); + addRenderableWidget(new Button(4, 4, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> { + minecraft.setScreen(parent); + })); + } + rulesList = addWidget(new AddonsList(minecraft, width, height, 30, height, BACKGROUND_LOCATION)); + ConfigAddonRegistryImpl addonRegistry = (ConfigAddonRegistryImpl) ConfigAddonRegistry.getInstance(); + for (ConfigAddon addon : addonRegistry.getAddons()) { + rulesList.addItem(new DefaultAddonEntry(parent, addon)); + } + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + this.rulesList.render(matrices, mouseX, mouseY, delta); + super.render(matrices, mouseX, mouseY, delta); + this.font.drawShadow(matrices, this.title.getVisualOrderText(), this.width / 2.0F - this.font.width(this.title) / 2.0F, 12.0F, -1); + } + + public static class AddonsList extends DynamicElementListWidget { + private boolean inFocus; + + public AddonsList(Minecraft client, int width, int height, int top, int bottom, ResourceLocation backgroundLocation) { + super(client, width, height, top, bottom, backgroundLocation); + } + + @Override + public boolean changeFocus(boolean lookForwards) { + if (!this.inFocus && this.getItemCount() == 0) { + return false; + } else { + this.inFocus = !this.inFocus; + if (this.inFocus && this.getSelectedItem() == null && this.getItemCount() > 0) { + this.moveSelection(1); + } else if (this.inFocus && this.getSelectedItem() != null) { + this.getSelectedItem(); + } + + ret