From dc92d0a4d262b633e9f322def3e89ab1a05417ab Mon Sep 17 00:00:00 2001 From: shedaniel Date: Fri, 5 Feb 2021 21:55:26 +0800 Subject: More Signed-off-by: shedaniel --- .../me/shedaniel/rei/RoughlyEnoughItemsCore.java | 10 +- .../gui/config/entry/FilteringAddRuleScreen.java | 195 +++++++ .../rei/api/gui/config/entry/FilteringEntry.java | 110 ++++ .../config/entry/FilteringRuleOptionsScreen.java | 240 ++++++++ .../api/gui/config/entry/FilteringRulesScreen.java | 247 ++++++++ .../rei/api/gui/config/entry/FilteringScreen.java | 522 +++++++++++++++++ .../rei/api/gui/config/entry/NoFilteringEntry.java | 90 +++ .../gui/config/entry/RecipeScreenTypeEntry.java | 112 ++++ .../api/gui/config/entry/ReloadPluginsEntry.java | 95 +++ .../entry/SearchFilterSyntaxHighlightingEntry.java | 114 ++++ .../shedaniel/rei/api/server/InputSlotCrafter.java | 2 +- .../me/shedaniel/rei/compat/LBASupportPlugin.java | 8 +- .../shedaniel/rei/gui/ContainerScreenOverlay.java | 8 +- .../java/me/shedaniel/rei/gui/DelegateScreen.java | 2 +- .../me/shedaniel/rei/gui/OverlaySearchField.java | 4 +- .../shedaniel/rei/gui/PreRecipeViewingScreen.java | 8 +- .../shedaniel/rei/gui/RecipeDisplayExporter.java | 2 +- .../me/shedaniel/rei/gui/RecipeViewingScreen.java | 23 +- .../rei/gui/VillagerRecipeViewingScreen.java | 19 +- .../gui/config/entry/FilteringAddRuleScreen.java | 195 ------- .../rei/gui/config/entry/FilteringEntry.java | 110 ---- .../config/entry/FilteringRuleOptionsScreen.java | 240 -------- .../rei/gui/config/entry/FilteringRulesScreen.java | 247 -------- .../rei/gui/config/entry/FilteringScreen.java | 522 ----------------- .../rei/gui/config/entry/NoFilteringEntry.java | 90 --- .../gui/config/entry/RecipeScreenTypeEntry.java | 112 ---- .../rei/gui/config/entry/ReloadPluginsEntry.java | 95 --- .../entry/SearchFilterSyntaxHighlightingEntry.java | 115 ---- .../shedaniel/rei/gui/credits/CreditsScreen.java | 2 +- .../java/me/shedaniel/rei/gui/modules/Menu.java | 4 +- .../me/shedaniel/rei/gui/modules/MenuEntry.java | 2 +- .../entries/EntryStackSubsetsMenuEntry.java | 2 +- .../rei/gui/modules/entries/GameModeMenuEntry.java | 2 +- .../gui/modules/entries/SubSubsetsMenuEntry.java | 2 +- .../rei/gui/modules/entries/WeatherMenuEntry.java | 2 +- .../rei/gui/plugin/DefaultRuntimePlugin.java | 31 +- .../rei/gui/plugin/entry/FluidEntryDefinition.java | 2 +- .../rei/gui/plugin/entry/ItemEntryDefinition.java | 6 +- .../shedaniel/rei/gui/widget/DraggableWidget.java | 1 + .../shedaniel/rei/gui/widget/EntryListWidget.java | 8 +- .../me/shedaniel/rei/gui/widget/EntryWidget.java | 4 +- .../rei/gui/widget/FavoritesListWidget.java | 11 +- .../me/shedaniel/rei/gui/widget/QueuedTooltip.java | 2 +- .../rei/gui/widget/RecipeChoosePageWidget.java | 8 +- .../me/shedaniel/rei/gui/widget/TabWidget.java | 8 +- .../me/shedaniel/rei/impl/AbstractEntryStack.java | 2 +- .../main/java/me/shedaniel/rei/impl/Animator.java | 5 +- .../me/shedaniel/rei/impl/ClientHelperImpl.java | 5 +- .../me/shedaniel/rei/impl/ConfigManagerImpl.java | 6 +- .../me/shedaniel/rei/impl/ConfigObjectImpl.java | 2 +- .../rei/impl/DisplayBoundsRegistryImpl.java | 4 +- .../me/shedaniel/rei/impl/EntryRegistryImpl.java | 2 +- .../me/shedaniel/rei/impl/ExclusionZonesImpl.java | 2 +- .../me/shedaniel/rei/impl/InternalWidgets.java | 14 +- .../me/shedaniel/rei/impl/RecipeRegistryImpl.java | 47 +- .../java/me/shedaniel/rei/impl/ScreenHelper.java | 9 +- .../java/me/shedaniel/rei/impl/SearchArgument.java | 4 +- .../rei/impl/entry/EmptyEntryDefinition.java | 6 +- .../rei/impl/filtering/FilteringContextImpl.java | 2 +- .../rei/impl/filtering/FilteringResult.java | 2 +- .../rei/impl/filtering/FilteringRule.java | 2 +- .../impl/filtering/rules/ManualFilteringRule.java | 2 +- .../impl/filtering/rules/SearchFilteringRule.java | 6 +- .../rei/impl/registry/CategoryRegistryImpl.java | 73 +++ .../rei/impl/subsets/SubsetsRegistryImpl.java | 2 +- .../me/shedaniel/rei/impl/widgets/ArrowWidget.java | 2 +- .../rei/impl/widgets/BurningFireWidget.java | 2 +- .../shedaniel/rei/impl/widgets/ButtonWidget.java | 6 +- .../shedaniel/rei/impl/widgets/DrawableWidget.java | 4 +- .../widgets/FillRectangleDrawableConsumer.java | 2 +- .../me/shedaniel/rei/impl/widgets/LabelWidget.java | 6 +- .../me/shedaniel/rei/impl/widgets/PanelWidget.java | 6 +- .../rei/impl/widgets/TextFieldWidget.java | 642 +++++++++++++++++++++ .../rei/impl/widgets/TexturedDrawableConsumer.java | 2 +- .../autocrafting/DefaultCategoryHandler.java | 2 +- .../shedaniel/rei/tests/plugin/REITestPlugin.java | 6 - 76 files changed, 2602 insertions(+), 1909 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringAddRuleScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRuleOptionsScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRulesScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/NoFilteringEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/RecipeScreenTypeEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/ReloadPluginsEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/SearchFilterSyntaxHighlightingEntry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringAddRuleScreen.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringEntry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringRuleOptionsScreen.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringRulesScreen.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringScreen.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/NoFilteringEntry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/RecipeScreenTypeEntry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/ReloadPluginsEntry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/gui/config/entry/SearchFilterSyntaxHighlightingEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/registry/CategoryRegistryImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/widgets/TextFieldWidget.java (limited to 'runtime/src/main/java') diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index 5c57aad51..c5baedb59 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -42,13 +42,13 @@ import me.shedaniel.rei.api.fluid.FluidSupportProvider; import me.shedaniel.rei.api.ingredient.util.EntryStacks; import me.shedaniel.rei.api.plugins.REIPluginV0; import me.shedaniel.rei.api.subsets.SubsetsRegistry; -import me.shedaniel.rei.api.util.DrawableConsumer; -import me.shedaniel.rei.api.util.Renderer; -import me.shedaniel.rei.api.widgets.*; +import me.shedaniel.rei.api.gui.DrawableConsumer; +import me.shedaniel.rei.api.gui.Renderer; +import me.shedaniel.rei.api.gui.widgets.*; import me.shedaniel.rei.gui.ContainerScreenOverlay; import me.shedaniel.rei.gui.widget.EntryWidget; import me.shedaniel.rei.gui.widget.QueuedTooltip; -import me.shedaniel.rei.gui.widget.Widget; +import me.shedaniel.rei.api.gui.widgets.Widget; import me.shedaniel.rei.impl.*; import me.shedaniel.rei.impl.entry.EmptyEntryDefinition; import me.shedaniel.rei.impl.subsets.SubsetsRegistryImpl; @@ -184,7 +184,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer { } @Override - public me.shedaniel.rei.api.widgets.Slot createSlot(Point point) { + public me.shedaniel.rei.api.gui.widgets.Slot createSlot(Point point) { return new EntryWidget(point); } diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringAddRuleScreen.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringAddRuleScreen.java new file mode 100644 index 000000000..7f8dc15fa --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringAddRuleScreen.java @@ -0,0 +1,195 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.gui.config.entry; + +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; +import me.shedaniel.rei.impl.filtering.FilteringRule; +import me.shedaniel.rei.impl.filtering.rules.ManualFilteringRule; +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.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; +import java.util.function.BiFunction; + +public class FilteringAddRuleScreen extends Screen { + private final FilteringEntry entry; + private RulesList rulesList; + Screen parent; + + public FilteringAddRuleScreen(FilteringEntry entry) { + super(new TranslatableComponent("config.roughlyenoughitems.filteringRulesScreen.new")); + this.entry = entry; + } + + @Override + public void init() { + super.init(); + { + Component backText = new TextComponent("↩ ").append(new TranslatableComponent("gui.back")); + addButton(new Button(4, 4, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> { + minecraft.setScreen(parent); + this.parent = null; + })); + } + rulesList = addWidget(new RulesList(minecraft, width, height, 30, height, BACKGROUND_LOCATION)); + for (FilteringRule rule : FilteringRule.REGISTRY) { + if (!(rule instanceof ManualFilteringRule)) + rulesList.addItem(new DefaultRuleEntry(parent, entry, rule.createNew(), null)); + } + rulesList.selectItem(rulesList.children().get(0)); + } + + @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 RulesList extends DynamicElementListWidget { + private boolean inFocus; + + public RulesList(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(); + } + + return this.inFocus; + } + } + + @Override + protected boolean isSelected(int index) { + return Objects.equals(this.getSelectedItem(), this.children().get(index)); + } + + @Override + protected int addItem(RuleEntry item) { + return super.addItem(item); + } + + @Override + public int getItemWidth() { + return width - 40; + } + + @Override + protected int getScrollbarPosition() { + return width - 14; + } + } + + public static abstract class RuleEntry extends DynamicElementListWidget.ElementEntry { + private final FilteringRule rule; + + public RuleEntry(FilteringRule rule) { + this.rule = rule; + } + + public FilteringRule getRule() { + return rule; + } + + @Override + public int getItemHeight() { + return 26; + } + + @Override + public boolean changeFocus(boolean lookForwards) { + return false; + } + } + + public static class DefaultRuleEntry extends RuleEntry { + private final Button addButton; + private final BiFunction screenFunction; + + public DefaultRuleEntry(Screen parent, FilteringEntry entry, FilteringRule rule, BiFunction screenFunction) { + super(rule); + this.screenFunction = (screenFunction == null ? rule.createEntryScreen().orElse(null) : screenFunction); + addButton = new Button(0, 0, 20, 20, Component.nullToEmpty(" + "), button -> { + entry.edited = true; + Minecraft.getInstance().setScreen(this.screenFunction.apply(entry, parent)); + entry.rules.add(0, rule); + }); + addButton.active = this.screenFunction != null; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + Minecraft client = Minecraft.getInstance(); + { + Component title = getRule().getTitle(); + int i = client.font.width(title); + if (i > entryWidth - 28) { + FormattedText titleTrimmed = FormattedText.composite(client.font.substrByWidth(title, entryWidth - 28 - client.font.width("...")), FormattedText.of("...")); + client.font.drawShadow(matrices, Language.getInstance().getVisualOrder(titleTrimmed), x + 2, y + 1, 16777215); + } else { + client.font.drawShadow(matrices, title.getVisualOrderText(), x + 2, y + 1, 16777215); + } + } + { + Component subtitle = getRule().getSubtitle(); + int i = client.font.width(subtitle); + if (i > entryWidth - 28) { + FormattedText subtitleTrimmed = FormattedText.composite(client.font.substrByWidth(subtitle, entryWidth - 28 - client.font.width("...")), FormattedText.of("...")); + client.font.drawShadow(matrices, Language.getInstance().getVisualOrder(subtitleTrimmed), x + 2, y + 12, 8421504); + } else { + client.font.drawShadow(matrices, subtitle.getVisualOrderText(), x + 2, y + 12, 8421504); + } + } + addButton.x = x + entryWidth - 25; + addButton.y = y + 1; + addButton.render(matrices, mouseX, mouseY, delta); + } + + @Override + public List children() { + return Collections.singletonList(addButton); + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringEntry.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringEntry.java new file mode 100644 index 000000000..79c71af8b --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringEntry.java @@ -0,0 +1,110 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.gui.config.entry; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.api.AbstractConfigListEntry; +import me.shedaniel.rei.api.ingredient.EntryStack; +import me.shedaniel.rei.api.ingredient.util.EntryStacks; +import me.shedaniel.rei.impl.filtering.FilteringRule; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.chat.NarratorChatListener; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.TranslatableComponent; +import org.jetbrains.annotations.ApiStatus; + +import java.util.*; +import java.util.function.Consumer; + +@ApiStatus.Internal +public class FilteringEntry extends AbstractConfigListEntry> { + private int width; + Consumer> saveConsumer; + Consumer>> rulesSaveConsumer; + List defaultValue; + Set configFiltered; + List> rules; + boolean edited = false; + final FilteringScreen filteringScreen = new FilteringScreen(this); + final FilteringRulesScreen filteringRulesScreen = new FilteringRulesScreen(this); + private final AbstractWidget buttonWidget = new Button(0, 0, 0, 20, new TranslatableComponent("config.roughlyenoughitems.filteringScreen"), button -> { + filteringRulesScreen.parent = Minecraft.getInstance().screen; + Minecraft.getInstance().setScreen(filteringRulesScreen); + }); + private final List children = ImmutableList.of(buttonWidget); + + public FilteringEntry(int width, List configFiltered, List> rules, List defaultValue, Consumer> saveConsumer, Consumer>> rulesSaveConsumer) { + super(NarratorChatListener.NO_TITLE, false); + this.width = width; + this.configFiltered = new TreeSet<>(Comparator.comparing(EntryStacks::hashIgnoreCount)); + this.configFiltered.addAll(configFiltered); + this.rules = Lists.newArrayList(rules); + this.defaultValue = defaultValue; + this.saveConsumer = saveConsumer; + this.rulesSaveConsumer = rulesSaveConsumer; + } + + @Override + public List getValue() { + return Lists.newArrayList(configFiltered); + } + + @Override + public Optional> getDefaultValue() { + return Optional.ofNullable(defaultValue); + } + + @Override + public void save() { + saveConsumer.accept(getValue()); + rulesSaveConsumer.accept(rules); + this.edited = false; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + super.render(matrices, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); + Window window = Minecraft.getInstance().getWindow(); + this.buttonWidget.active = this.isEditable(); + this.buttonWidget.y = y; + this.buttonWidget.x = x + entryWidth / 2 - width / 2; + this.buttonWidget.setWidth(width); + this.buttonWidget.render(matrices, mouseX, mouseY, delta); + } + + @Override + public List children() { + return children; + } + + @Override + public boolean isEdited() { + return super.isEdited() || edited; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRuleOptionsScreen.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRuleOptionsScreen.java new file mode 100644 index 000000000..cc951f58a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRuleOptionsScreen.java @@ -0,0 +1,240 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.gui.config.entry; + +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; +import me.shedaniel.rei.impl.filtering.FilteringRule; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class FilteringRuleOptionsScreen> extends Screen { + private final FilteringEntry entry; + private RulesList rulesList; + Screen parent; + public T rule; + + public FilteringRuleOptionsScreen(FilteringEntry entry, T rule, Screen screen) { + super(new TranslatableComponent("config.roughlyenoughitems.filteringRulesScreen")); + this.entry = entry; + this.rule = rule; + this.parent = screen; + } + + @Override + public void init() { + super.init(); + if (rulesList != null) save(); + { + Component doneText = new TranslatableComponent("gui.done"); + int width = Minecraft.getInstance().font.width(doneText); + addButton(new Button(this.width - 4 - width - 10, 4, width + 10, 20, doneText, button -> { + save(); + minecraft.setScreen(parent); + })); + } + rulesList = addWidget(new RulesList(minecraft, width, height, 30, height, BACKGROUND_LOCATION)); + addEntries(ruleEntry -> rulesList.addItem(ruleEntry)); + } + + public abstract void addEntries(Consumer entryConsumer); + + public abstract void save(); + + public void addText(Consumer entryConsumer, FormattedText text) { + for (FormattedCharSequence s : font.split(text, width - 80)) { + entryConsumer.accept(new TextRuleEntry(rule, s)); + } + } + + public void addEmpty(Consumer entryConsumer, int height) { + entryConsumer.accept(new EmptyRuleEntry(rule, height)); + } + + @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 RulesList extends DynamicElementListWidget { + public RulesList(Minecraft client, int width, int height, int top, int bottom, ResourceLocation backgroundLocation) { + super(client, width, height, top, bottom, backgroundLocation); + } + + @Override + protected int addItem(RuleEntry item) { + return super.addItem(item); + } + + @Override + public int getItemWidth() { + return width - 40; + } + + @Override + protected int getScrollbarPosition() { + return width - 14; + } + } + + public static abstract class RuleEntry extends DynamicElementListWidget.ElementEntry { + private final FilteringRule rule; + + public RuleEntry(FilteringRule rule) { + this.rule = rule; + } + + public FilteringRule getRule() { + return rule; + } + } + + public static class TextRuleEntry extends RuleEntry { + private final FormattedCharSequence text; + + public TextRuleEntry(FilteringRule rule, FormattedCharSequence text) { + super(rule); + this.text = text; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + Minecraft.getInstance().font.drawShadow(matrices, text, x + 5, y, -1); + } + + @Override + public int getItemHeight() { + return 12; + } + + @Override + public List children() { + return Collections.emptyList(); + } + } + + public static class EmptyRuleEntry extends RuleEntry { + private final int height; + + public EmptyRuleEntry(FilteringRule rule, int height) { + super(rule); + this.height = height; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + } + + @Override + public int getItemHeight() { + return height; + } + + @Override + public List children() { + return Collections.emptyList(); + } + } + + public static class TextFieldRuleEntry extends RuleEntry { + private final EditBox widget; + + public TextFieldRuleEntry(int width, FilteringRule rule, Consumer widgetConsumer) { + super(rule); + this.widget = new EditBox(Minecraft.getInstance().font, 0, 0, width, 18, Component.nullToEmpty("")); + widgetConsumer.accept(widget); + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + widget.x = x + 2; + widget.y = y + 2; + widget.render(matrices, mouseX, mouseY, delta); + } + + @Override + public int getItemHeight() { + return 20; + } + + public EditBox getWidget() { + return widget; + } + + @Override + public List children() { + return Collections.singletonList(widget); + } + } + + public static class BooleanRuleEntry extends RuleEntry { + private boolean b; + private final Button widget; + + public BooleanRuleEntry(int width, boolean b, FilteringRule rule, Function textFunction) { + super(rule); + this.b = b; + this.widget = new Button(0, 0, 100, 20, textFunction.apply(b), button -> { + this.b = !this.b; + button.setMessage(textFunction.apply(this.b)); + }); + } + + public boolean getBoolean() { + return b; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + widget.x = x + 2; + widget.y = y; + widget.render(matrices, mouseX, mouseY, delta); + } + + @Override + public int getItemHeight() { + return 20; + } + + @Override + public List children() { + return Collections.singletonList(widget); + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRulesScreen.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRulesScreen.java new file mode 100644 index 000000000..daffac707 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringRulesScreen.java @@ -0,0 +1,247 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.gui.config.entry; + +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; +import me.shedaniel.rei.impl.filtering.FilteringRule; +import me.shedaniel.rei.impl.filtering.rules.ManualFilteringRule; +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.screens.Screen; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +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 net.minecraft.sounds.SoundEvents; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +import static me.shedaniel.rei.gui.RecipeViewingScreen.CHEST_GUI_TEXTURE; + +public class FilteringRulesScreen extends Screen { + private final FilteringEntry entry; + private RulesList rulesList; + Screen parent; + + public FilteringRulesScreen(FilteringEntry entry) { + super(new TranslatableComponent("config.roughlyenoughitems.filteringRulesScreen")); + this.entry = entry; + } + + @Override + public void init() { + super.init(); + { + Component backText = new TextComponent("↩ ").append(new TranslatableComponent("gui.back")); + addButton(new Button(4, 4, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> { + minecraft.setScreen(parent); + this.parent = null; + })); + } + { + Component addText = new TextComponent(" + "); + addButton(new Button(width - 4 - 20, 4, 20, 20, addText, button -> { + FilteringAddRuleScreen screen = new FilteringAddRuleScreen(entry); + screen.parent = this; + minecraft.setScreen(screen); + })); + } + rulesList = addWidget(new RulesList(minecraft, width, height, 30, height, BACKGROUND_LOCATION)); + for (int i = entry.rules.size() - 1; i >= 0; i--) { + FilteringRule rule = entry.rules.get(i); + if (rule instanceof ManualFilteringRule) + rulesList.addItem(new DefaultRuleEntry(rule, entry, (entry, screen) -> { + entry.filteringScreen.parent = screen; + return entry.filteringScreen; + })); + else rulesList.addItem(new DefaultRuleEntry(rule, entry, null)); + } + rulesList.selectItem(rulesList.children().get(0)); + } + + @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 RulesList extends DynamicElementListWidget { + private boolean inFocus; + + public RulesList(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(); + } + + return this.inFocus; + } + } + + @Override + protected boolean isSelected(int index) { + return Objects.equals(this.getSelectedItem(), this.children().get(index)); + } + + @Override + protected int addItem(RuleEntry item) { + return super.addItem(item); + } + + @Override + public boolean mouseClicked(double double_1, double double_2, int int_1) { + if (super.mouseClicked(double_1, double_2, int_1)) + return true; + RuleEntry item = getItemAtPosition(double_1, double_2); + if (item != null) { + client.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + selectItem(item); + this.setFocused(item); + this.setDragging(true); + return true; + } + return false; + } + + @Override + public int getItemWidth() { + return width - 40; + } + + @Override + protected int getScrollbarPosition() { + return width - 14; + } + } + + public static abstract class RuleEntry extends DynamicElementListWidget.ElementEntry { + private final FilteringRule rule; + + public RuleEntry(FilteringRule rule) { + this.rule = rule; + } + + public FilteringRule getRule() { + return rule; + } + + @Override + public int getItemHeight() { + return 26; + } + + @Override + public boolean changeFocus(boolean lookForwards) { + return false; + } + } + + public static class DefaultRuleEntry extends RuleEntry { + private final Button configureButton; + private final Button deleteButton; + private final BiFunction screenFunction; + + public DefaultRuleEntry(FilteringRule rule, FilteringEntry entry, BiFunction screenFunction) { + super(rule); + this.screenFunction = (screenFunction == null ? rule.createEntryScreen().orElse(null) : screenFunction); + configureButton = new Button(0, 0, 20, 20, Component.nullToEmpty(null), button -> { + entry.edited = true; + Minecraft.getInstance().setScreen(this.screenFunction.apply(entry, Minecraft.getInstance().screen)); + }) { + @Override + protected void renderBg(PoseStack matrices, Minecraft client, int mouseX, int mouseY) { + super.renderBg(matrices, client, mouseX, mouseY); + Minecraft.getInstance().getTextureManager().bind(CHEST_GUI_TEXTURE); + blit(matrices, x + 3, y + 3, 0, 0, 14, 14); + } + }; + { + Component deleteText = new TranslatableComponent("config.roughlyenoughitems.filteringRulesScreen.delete"); + deleteButton = new Button(0, 0, Minecraft.getInstance().font.width(deleteText) + 10, 20, deleteText, button -> { + final Screen screen = Minecraft.getInstance().screen; + entry.edited = true; + entry.rules.remove(rule); + screen.init(Minecraft.getInstance(), screen.width, screen.height); + }); + } + configureButton.active = this.screenFunction != null; + deleteButton.active = !(rule instanceof ManualFilteringRule); + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + Minecraft client = Minecraft.getInstance(); + { + Component title = getRule().getTitle(); + int i = client.font.width(title); + if (i > entryWidth - 28) { + FormattedText titleTrimmed = FormattedText.composite(client.font.substrByWidth(title, entryWidth - 28 - client.font.width("...")), FormattedText.of("...")); + client.font.drawShadow(matrices, Language.getInstance().getVisualOrder(titleTrimmed), x + 2, y + 1, 16777215); + } else { + client.font.drawShadow(matrices, title.getVisualOrderText(), x + 2, y + 1, 16777215); + } + } + { + Component subtitle = getRule().getSubtitle(); + int i = client.font.width(subtitle); + if (i > entryWidth - 28) { + FormattedText subtitleTrimmed = FormattedText.composite(client.font.substrByWidth(subtitle, entryWidth - 28 - client.font.width("...")), FormattedText.of("...")); + client.font.drawShadow(matrices, Language.getInstance().getVisualOrder(subtitleTrimmed), x + 2, y + 12, 8421504); + } else { + client.font.drawShadow(matrices, subtitle.getVisualOrderText(), x + 2, y + 12, 8421504); + } + } + configureButton.x = x + entryWidth - 25; + configureButton.y = y + 1; + configureButton.render(matrices, mouseX, mouseY, delta); + deleteButton.x = x + entryWidth - 27 - deleteButton.getWidth(); + deleteButton.y = y + 1; + deleteButton.render(matrices, mouseX, mouseY, delta); + } + + @Override + public List children() { + return Arrays.asList(configureButton, deleteButton); + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java new file mode 100644 index 000000000..59f96faed --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java @@ -0,0 +1,522 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.gui.config.entry; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import me.shedaniel.clothconfig2.ClothConfigInitializer; +import me.shedaniel.clothconfig2.api.ScissorsHandler; +import me.shedaniel.clothconfig2.api.ScrollingContainer; +import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWidget; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.math.impl.PointHelper; +import me.shedaniel.rei.api.ConfigObject; +import me.shedaniel.rei.api.EntryRegistry; +import me.shedaniel.rei.api.ingredient.EntryStack; +import me.shedaniel.rei.api.REIHelper; +import me.shedaniel.rei.api.gui.widgets.Tooltip; +import me.shedaniel.rei.gui.OverlaySearchField; +import me.shedaniel.rei.gui.widget.EntryWidget; +import me.shedaniel.rei.impl.ScreenHelper; +import me.shedaniel.rei.impl.SearchArgument; +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.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static me.shedaniel.rei.gui.widget.EntryListWidget.entrySize; + +@ApiStatus.Internal +public class FilteringScreen extends Screen { + protected List selected = Lists.newArrayList(); + protected final ScrollingContainer scrolling = new ScrollingContainer() { + @Override + public int getMaxScrollHeight() { + return Mth.ceil(entryStacks.size() / (innerBounds.width / (float) entrySize())) * entrySize() + 28; + } + + @Override + public Rectangle getBounds() { + return FilteringScreen.this.getBounds(); + } + + @Override + public int getScrollBarX() { + return width - 7; + } + }; + + Screen parent; + private FilteringEntry filteringEntry; + private Tooltip tooltip = null; + private List entryStacks = null; + private Rectangle innerBounds; + private List entries = Collections.emptyList(); + private List elements = Collections.emptyList(); + + private Point selectionPoint = null; + private Point secondPoint = null; + + private OverlaySearchField searchField; + private Button selectAllButton; + private Button selectNoneButton; + private Button hideButton; + private Button showButton; + private Button backButton; + private Rectangle selectionCache; + + private List lastSearchArguments = Collections.emptyList(); + + public FilteringScreen(FilteringEntry filteringEntry) { + super(new TranslatableComponent("config.roughlyenoughitems.filteringScreen")); + this.filteringEntry = filteringEntry; + this.searchField = new OverlaySearchField(0, 0, 0, 0); + { + Component selectAllText = new TranslatableComponent("config.roughlyenoughitems.filteredEntries.selectAll"); + this.selectAllButton = new Button(0, 0, Minecraft.getInstance().font.width(selectAllText) + 10, 20, selectAllText, button -> { + this.selectionPoint = new Point(-Integer.MAX_VALUE / 2, -Integer.MAX_VALUE / 2); + this.secondPoint = new Point(Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2); + }); + } + { + Component selectNoneText = new TranslatableComponent("config.roughlyenoughitems.filteredEntries.selectNone"); + this.selectNoneButton = new Button(0, 0, Minecraft.getInstance().font.width(selectNoneText) + 10, 20, selectNoneText, button -> { + this.selectionPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + this.secondPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + }); + } + { + Component hideText = new TranslatableComponent("config.roughlyenoughitems.filteredEntries.hide"); + this.hideButton = new Button(0, 0, Minecraft.getInstance().font.width(hideText) + 10, 20, hideText, button -> { + for (int i = 0; i < entryStacks.size(); i++) { + EntryStack stack = entryStacks.get(i); + EntryListEntry entry = entries.get(i); + entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount); + if (entry.isSelected() && !entry.isFiltered()) { + filteringEntry.configFiltered.add(stack); + filteringEntry.edited = true; + entry.dirty = true; + } + } + }); + } + { + Component showText = new TranslatableComponent("config.roughlyenoughitems.filteredEntries.show"); + this.showButton = new Button(0, 0, Minecraft.getInstance().font.width(showText) + 10, 20, showText, button -> { + for (int i = 0; i < entryStacks.size(); i++) { + EntryStack stack = entryStacks.get(i); + EntryListEntry entry = entries.get(i); + entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount); + if (entry.isSelected() && filteringEntry.configFiltered.remove(stack)) { + filteringEntry.edited = true; + entry.dirty = true; + } + } + }); + } + { + Component backText = new TextComponent("↩ ").append(new TranslatableComponent("gui.back")); + this.backButton = new Button(0, 0, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> { + minecraft.setScreen(parent); + this.parent = null; + }); + } + this.searchField.isMain = false; + } + + private static Rectangle updateInnerBounds(Rectangle bounds) { + int width = Math.max(Mth.floor((bounds.width - 2 - 6) / (float) entrySize()), 1); + return new Rectangle((int) (bounds.getCenterX() - width * entrySize() / 2f), bounds.y + 5, width * entrySize(), bounds.height); + } + + public Rectangle getBounds() { + return new Rectangle(0, 30, width, this.height - 30); + } + + @Override + public void init() { + super.init(); + Rectangle bounds = getBounds(); + updateSearch(this.searchField.getText()); + this.selectAllButton.x = 2; + this.selectAllButton.y = bounds.getMaxY() - 22; + this.selectNoneButton.x = 4 + selectAllButton.getWidth(); + this.selectNoneButton.y = bounds.getMaxY() - 22; + int searchFieldWidth = Math.max(bounds.width - (selectNoneButton.x + selectNoneButton.getWidth() + hideButton.getWidth() + showButton.getWidth() + 12), 100); + this.searchField.getBounds().setBounds(selectNoneButton.x + selectNoneButton.getWidth() + 4, bounds.getMaxY() - 21, searchFieldWidth, 18); + this.hideButton.x = bounds.getMaxX() - hideButton.getWidth() - showButton.getWidth() - 4; + this.hideButton.y = bounds.getMaxY() - 22; + this.showButton.x = bounds.getMaxX() - showButton.getWidth() - 2; + this.showButton.y = bounds.getMaxY() - 22; + this.backButton.x = 4; + this.backButton.y = 4; + this.searchField.setResponder(this::updateSearch); + } + + protected void renderHoleBackground(PoseStack matrices, int y1, int y2, int tint, int alpha1, int alpha2) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + this.minecraft.getTextureManager().bind(BACKGROUND_LOCATION); + Matrix4f matrix = matrices.last().pose(); + RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + float float_1 = 32.0F; + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, y2, 0.0F).uv(0.0F, y2 / 32.0F).color(tint, tint, tint, alpha2).endVertex(); + buffer.vertex(matrix, this.width, y2, 0.0F).uv(this.width / 32.0F, y2 / 32.0F).color(tint, tint, tint, alpha2).endVertex(); + buffer.vertex(matrix, this.width, y1, 0.0F).uv(this.width / 32.0F, y1 / 32.0F).color(tint, tint, tint, alpha1).endVertex(); + buffer.vertex(matrix, 0, y1, 0.0F).uv(0.0F, y1 / 32.0F).color(tint, tint, tint, alpha1).endVertex(); + tesselator.end(); + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + renderHoleBackground(matrices, 0, height, 32, 255, 255); + updateSelectionCache(); + Rectangle bounds = getBounds(); + tooltip = null; + if (bounds.isEmpty()) + return; + ScissorsHandler.INSTANCE.scissor(bounds); + for (EntryListEntry entry : entries) + entry.clearStacks(); + int skip = Math.max(0, Mth.floor(scrolling.scrollAmount / (float) entrySize())); + int nextIndex = skip * innerBounds.width / entrySize(); + int i = nextIndex; + for (; i < entryStacks.size(); i++) { + EntryStack stack = entryStacks.get(i); + EntryListEntry entry = entries.get(nextIndex); + entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount); + if (entry.getBounds().y > bounds.getMaxY()) + break; + entry.entry(stack); + entry.render(matrices, mouseX, mouseY, delta); + nextIndex++; + } + updatePosition(delta); + scrolling.renderScrollBar(0xff000000, 1, REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f); + matrices.pushPose(); + matrices.translate(0, 0, 300); + this.searchField.laterRender(matrices, mouseX, mouseY, delta); + this.selectAllButton.render(matrices, mouseX, mouseY, delta); + this.selectNoneButton.render(matrices, mouseX, mouseY, delta); + this.hideButton.render(matrices, mouseX, mouseY, delta); + this.showButton.render(matrices, mouseX, mouseY, delta); + matrices.popPose(); + + ScissorsHandler.INSTANCE.removeLastScissor(); + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder buffer = tessellator.getBuilder(); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(770, 771, 0, 1); + RenderSystem.disableAlphaTest(); + RenderSystem.shadeModel(7425); + RenderSystem.disableTexture(); + Matrix4f matrix = matrices.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + buffer.vertex(matrix, 0, bounds.y + 4, 0.0F).uv(0.0F, 1.0F).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, bounds.y + 4, 0.0F).uv(1.0F, 1.0F).color(0, 0, 0, 0).endVertex(); + buffer.vertex(matrix, width, bounds.y, 0.0F).uv(1.0F, 0.0F).color(0, 0, 0, 255).endVertex(); + buffer.vertex(matrix, 0, bounds.y, 0.0F).uv(0.0F, 0.0F).color(0, 0, 0, 255).endVertex(); + tessellator.end(); + RenderSystem.enableTexture(); + RenderSystem.shadeModel(7424); + RenderSystem.enableAlphaTest(); + RenderSystem.disableBlend(); + renderHoleBackground(matrices, 0, bounds.y, 64, 255, 255); + + this.backButton.render(matrices, mouseX, mouseY, delta); + + if (tooltip != null) { + ScreenHelper.getLastOverlay().renderTooltip(matrices, tooltip); + } + + this.font.drawShadow(matrices, this.title.getVisualOrderText(), this.width / 2.0F - this.font.width(this.title) / 2.0F, 12.0F, -1); + } + + private Rectangle getSelection() { + return selectionCache; + } + + private void updateSelectionCache() { + if (selectionPoint != null) { + Point p = secondPoint; + if (p == null) { + p = PointHelper.ofMouse(); + p.translate(0, (int) scrolling.scrollAmount); + } + int left = Math.min(p.x, selectionPoint.x); + int top = Math.min(p.y, selectionPoint.y); + int right = Math.max(p.x, selectionPoint.x); + int bottom = Math.max(p.y, selectionPoint.y); + selectionCache = new Rectangle(left, (int) (top - scrolling.scrollAmount), right - left, bottom - top); + return; + } + selectionCache = new Rectangle(0, 0, 0, 0); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double dx, double dy) { + if (scrolling.mouseDragged(mouseX, mouseY, button, dx, dy, ConfigObject.getInstance().doesSnapToRows(), entrySize())) + return true; + return super.mouseDragged(mouseX, mouseY, button, dx, dy); + } + + private void updatePosition(float delta) { + if (ConfigObject.getInstance().doesSnapToRows() && scrolling.scrollTarget >= 0 && scrolling.scrollTarget <= scrolling.getMaxScroll()) { + double nearestRow = Math.round(scrolling.scrollTarget / (double) entrySize()) * (double) entrySize(); + if (!DynamicNewSmoothScrollingEntryListWidget.Precision.almostEquals(scrolling.scrollTarget, nearestRow, DynamicNewSmoothScrollingEntryListWidget.Precision.FLOAT_EPSILON)) + scrolling.scrollTarget += (nearestRow - scrolling.scrollTarget) * Math.min(delta / 2.0, 1.0); + else + scrolling.scrollTarget = nearestRow; + } + scrolling.updatePosition(delta); + } + + public void updateSearch(String searchTerm) { + lastSearchArguments = SearchArgument.processSearchTerm(searchTerm); + Set> list = Sets.newLinkedHashSet(); + EntryRegistry.getInstance().getEntryStacks().forEach(stack -> { + if (canLastSearchTermsBeAppliedTo(stack)) { + list.add(stack.copy().setting(EntryStack.Settings.CHECK_AMOUNT, EntryStack.Settings.FALSE).setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.CHECK_TAGS, EntryStack.Settings.TRUE)); + } + }); + + entryStacks = Lists.newArrayList(list); + updateEntriesPosition(); + } + + public boolean canLastSearchTermsBeAppliedTo(EntryStack stack) { + return lastSearchArguments.isEmpty() || SearchArgument.canSearchTermsBeAppliedTo(stack, lastSearchArguments); + } + + public void updateEntriesPosition() { + int entrySize = entrySize(); + this.innerBounds = updateInnerBounds(getBounds()); + int width = innerBounds.width / entrySize; + int pageHeight = innerBounds.height / entrySize; + int slotsToPrepare = Math.max(entryStacks.size() * 3, width * pageHeight * 3); + int currentX = 0; + int currentY = 0; + List entries = Lists.newArrayList(); + for (int i = 0; i < slotsToPrepare; i++) { + int xPos = currentX * entrySize + innerBounds.x; + int yPos = currentY * entrySize + innerBounds.y; + entries.add(new EntryListEntry(xPos, yPos, entrySize)); + currentX++; + if (currentX >= width) { + currentX = 0; + currentY++; + } + } + this.entries = entries; + this.elements = Lists.newArrayList(entries); + this.elements.add(searchField); + } + + @Override + public List children() { + return elements; + } + + @Override + public boolean mouseClicked(double double_1, double double_2, int int_1) { + if (scrolling.updateDraggingState(double_1, double_2, int_1)) + return true; + + if (getBounds().contains(double_1, double_2)) { + if (searchField.mouseClicked(double_1, double_2, int_1)) { + this.selectionPoint = null; + this.secondPoint = null; + return true; + } else if (selectAllButton.mouseClicked(double_1, double_2, int_1)) { + return true; + } else if (selectNoneButton.mouseClicked(double_1, double_2, int_1)) { + return true; + } else if (hideButton.mouseClicked(double_1, double_2, int_1)) { + return true; + } else if (showButton.mouseClicked(double_1, double_2, int_1)) { + return true; + } + if (int_1 == 0) { + this.selectionPoint = new Point(double_1, double_2 + scrolling.scrollAmount); + this.secondPoint = null; + return true; + } + } + if (backButton.mouseClicked(double_1, double_2, int_1)) { + return true; + } + return false; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (selectionPoint != null && button == 0 && secondPoint == null) { + this.secondPoint = new Point(mouseX, mouseY + scrolling.scrollAmount); + if (secondPoint.equals(selectionPoint)) { + secondPoint.translate(1, 1); + } + return true; + } + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean charTyped(char chr, int keyCode) { + for (GuiEventListener element : children()) + if (element.charTyped(chr, keyCode)) + return true; + return super.charTyped(chr, keyCode); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + for (GuiEventListener element : children()) + if (element.keyPressed(keyCode, scanCode, modifiers)) + return true; + if (Screen.isSelectAll(keyCode)) { + this.selectionPoint = new Point(0, 0); + this.secondPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + return true; + } + if (keyCode == 256 && this.shouldCloseOnEsc()) { + this.backButton.onPress(); + return true; + } else if (keyCode == 258) { + boolean bl = !hasShiftDown(); + if (!this.changeFocus(bl)) { + this.changeFocus(bl); + } + + return true; + } + return false; + } + + public void updateArea(@Nullable String searchTerm) { + if (searchTerm != null) + updateSearch(searchTerm); + else if (entryStacks == null) + updateSearch(""); + else + updateEntriesPosition(); + } + + @Override + public boolean mouseScrolled(double double_1, double double_2, double double_3) { + if (getBounds().contains(double_1, double_2)) { + scrolling.offset(ClothConfigInitializer.getScrollStep() * -double_3, true); + return true; + } + super.mouseScrolled(double_1, double_2, double_3); + return true; + } + + private class EntryListEntry extends EntryWidget { + private int backupY; + private boolean filtered = false; + private boolean dirty = true; + + priva