aboutsummaryrefslogtreecommitdiff
path: root/runtime/src
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2023-11-07 17:38:48 +0800
committershedaniel <daniel@shedaniel.me>2024-04-16 00:38:18 +0900
commitd8008dc4e83afc5428814e95c7c2a456c608e053 (patch)
treee322284c983bbf8f5ed5e758e3fe94e694441410 /runtime/src
parent54ffa6bc8868e8e5e1337806d0a681b316096cde (diff)
downloadRoughlyEnoughItems-d8008dc4e83afc5428814e95c7c2a456c608e053.tar.gz
RoughlyEnoughItems-d8008dc4e83afc5428814e95c7c2a456c608e053.tar.bz2
RoughlyEnoughItems-d8008dc4e83afc5428814e95c7c2a456c608e053.zip
Implement Config Search
Diffstat (limited to 'runtime/src')
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java99
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java46
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigEntriesListWidget.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigGroupWidget.java7
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigOptionWidget.java7
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchListWidget.java127
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchWidget.java373
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigCategories.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/CompositeOption.java32
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/OptionCategory.java14
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/OptionGroup.java22
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/text/TextTransformations.java37
-rwxr-xr-xruntime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json1
-rw-r--r--runtime/src/main/resources/assets/roughlyenoughitems/textures/gui/config/search_options.pngbin0 -> 220 bytes
16 files changed, 716 insertions, 57 deletions
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java
index 768e8355a..cfb1b6c64 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java
@@ -31,6 +31,7 @@ import me.shedaniel.clothconfig2.api.Modifier;
import me.shedaniel.clothconfig2.api.ModifierKeyCode;
import me.shedaniel.math.Point;
import me.shedaniel.math.Rectangle;
+import me.shedaniel.math.impl.PointHelper;
import me.shedaniel.rei.api.client.REIRuntime;
import me.shedaniel.rei.api.client.gui.widgets.Widget;
import me.shedaniel.rei.api.client.gui.widgets.Widgets;
@@ -43,8 +44,11 @@ import me.shedaniel.rei.impl.client.config.ConfigObjectImpl;
import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl;
import me.shedaniel.rei.impl.client.gui.config.components.ConfigCategoriesListWidget;
import me.shedaniel.rei.impl.client.gui.config.components.ConfigEntriesListWidget;
+import me.shedaniel.rei.impl.client.gui.config.components.ConfigSearchListWidget;
import me.shedaniel.rei.impl.client.gui.config.options.*;
import me.shedaniel.rei.impl.client.gui.modules.Menu;
+import me.shedaniel.rei.impl.client.gui.widget.HoleWidget;
+import me.shedaniel.rei.impl.client.gui.widget.basewidgets.TextFieldWidget;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.events.GuiEventListener;
@@ -59,15 +63,17 @@ import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
+import static me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils.literal;
import static me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils.translatable;
public class REIConfigScreen extends Screen implements ConfigAccess {
private final Screen parent;
private final List<OptionCategory> categories;
private final List<Widget> widgets = new ArrayList<>();
- private final Map<CompositeOption<?>, ?> defaultOptions = new HashMap<>();
- private final Map<CompositeOption<?>, ?> options = new HashMap<>();
+ private final Map<String, ?> defaultOptions = new HashMap<>();
+ private final Map<String, ?> options = new HashMap<>();
private OptionCategory activeCategory;
+ private boolean searching;
@Nullable
private Menu menu;
@Nullable
@@ -91,8 +97,8 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
for (OptionCategory category : this.categories) {
for (OptionGroup group : category.getGroups()) {
for (CompositeOption<?> option : group.getOptions()) {
- ((Map<CompositeOption<?>, Object>) this.defaultOptions).put(option, option.getBind().apply(defaultConfig));
- ((Map<CompositeOption<?>, Object>) this.options).put(option, option.getBind().apply(config));
+ ((Map<String, Object>) this.defaultOptions).put(option.getId(), option.getBind().apply(defaultConfig));
+ ((Map<String, Object>) this.options).put(option.getId(), option.getBind().apply(config));
}
}
}
@@ -107,7 +113,7 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
for (OptionGroup group : category.getGroups()) {
group.getOptions().replaceAll(option -> {
if (option.isRequiresLevel()) {
- return new CompositeOption<>(option.getName(), option.getDescription(), i -> 0, (i, v) -> new Object())
+ return new CompositeOption<>(option.getId(), option.getName(), option.getDescription(), i -> 0, (i, v) -> new Object())
.entry(value -> translatable("config.rei.texts.requires_level").withStyle(ChatFormatting.RED))
.defaultValue(() -> 1);
} else {
@@ -124,27 +130,53 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
this.widgets.clear();
this.widgets.add(Widgets.createLabel(new Point(width / 2, 12), this.title));
int sideWidth = (int) Math.round(width / 4.2);
- boolean singlePane = width - 20 - sideWidth <= 330;
- int singleSideWidth = 32 + 6 + 4;
- Mutable<Widget> list = new MutableObject<>(createEntriesList(singlePane, singleSideWidth, sideWidth));
- IntValue selectedCategory = new IntValue() {
- @Override
- public void accept(int index) {
- REIConfigScreen.this.activeCategory = categories.get(index);
- list.setValue(createEntriesList(singlePane, singleSideWidth, sideWidth));
- }
-
- @Override
- public int getAsInt() {
- return categories.indexOf(activeCategory);
- }
- };
- if (!singlePane) {
- this.widgets.add(ConfigCategoriesListWidget.create(new Rectangle(8, 32, sideWidth, height - 32 - 32), categories, selectedCategory));
+ if (this.searching) {
+ this.widgets.add(Widgets.createButton(new Rectangle(8, 32, sideWidth, 20), literal("↩ ").append(translatable("gui.back")))
+ .onClick(button -> setSearching(false)));
+ this.widgets.add(HoleWidget.createBackground(new Rectangle(8 + sideWidth + 4, 32, width - 16 - sideWidth - 4, 20), () -> 0, 32));
+ TextFieldWidget textField = new TextFieldWidget(new Rectangle(8 + sideWidth + 4 + 6, 32 + 6, width - 16 - sideWidth - 4 - 10, 12)) {
+ @Override
+ protected void renderSuggestion(PoseStack matrices, int x, int y) {
+ int color;
+ if (containsMouse(PointHelper.ofMouse()) || isFocused()) {
+ color = 0xddeaeaea;
+ } else {
+ color = -6250336;
+ }
+ this.font.drawShadow(matrices, this.font.plainSubstrByWidth(this.getSuggestion(), this.getWidth()), x, y, color);
+ }
+ };
+ textField.setHasBorder(false);
+ textField.setMaxLength(9000);
+ this.widgets.add(textField);
+ this.widgets.add(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> {
+ textField.setSuggestion(!textField.isFocused() && textField.getText().isEmpty() ? I18n.get("config.rei.texts.search_options") : null);
+ }));
+ this.widgets.add(ConfigSearchListWidget.create(this, this.categories, textField, new Rectangle(8, 32 + 20 + 4, width - 16, height - 32 - (32 + 20 + 4))));
} else {
- this.widgets.add(ConfigCategoriesListWidget.createTiny(new Rectangle(8, 32, singleSideWidth - 4, height - 32 - 32), categories, selectedCategory));
+ boolean singlePane = width - 20 - sideWidth <= 330;
+ int singleSideWidth = 32 + 6 + 4;
+ Mutable<Widget> list = new MutableObject<>(createEntriesList(singlePane, singleSideWidth, sideWidth));
+ IntValue selectedCategory = new IntValue() {
+ @Override
+ public void accept(int index) {
+ REIConfigScreen.this.activeCategory = categories.get(index);
+ list.setValue(createEntriesList(singlePane, singleSideWidth, sideWidth));
+ }
+
+ @Override
+ public int getAsInt() {
+ return categories.indexOf(activeCategory);
+ }
+ };
+ if (!singlePane) {
+ this.widgets.add(ConfigCategoriesListWidget.create(new Rectangle(8, 32, sideWidth, height - 32 - 32), categories, selectedCategory));
+ } else {
+ this.widgets.add(ConfigCategoriesListWidget.createTiny(new Rectangle(8, 32, singleSideWidth - 4, height - 32 - 32), categories, selectedCategory));
+ }
+ this.widgets.add(Widgets.delegate(list::getValue));
}
- this.widgets.add(Widgets.delegate(list::getValue));
+
this.widgets.add(Widgets.createButton(new Rectangle(width / 2 - 150 - 10, height - 26, 150, 20), translatable("gui.cancel")).onClick(button -> {
Minecraft.getInstance().setScreen(this.parent);
}));
@@ -171,11 +203,11 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
return ConfigEntriesListWidget.create(this, new Rectangle(singlePane ? 8 + singleSideWidth : 12 + sideWidth, 32, singlePane ? width - 16 - singleSideWidth : width - 20 - sideWidth, height - 32 - 32), activeCategory.getGroups());
}
- public Map<CompositeOption<?>, ?> getDefaultOptions() {
+ public Map<String, ?> getDefaultOptions() {
return defaultOptions;
}
- public Map<CompositeOption<?>, ?> getOptions() {
+ public Map<String, ?> getOptions() {
return options;
}
@@ -363,17 +395,17 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
@Override
public <T> T get(CompositeOption<T> option) {
- return (T) getOptions().get(option);
+ return (T) getOptions().get(option.getId());
}
@Override
public <T> void set(CompositeOption<T> option, T value) {
- ((Map<CompositeOption<?>, Object>) getOptions()).put(option, value);
+ ((Map<String, Object>) getOptions()).put(option.getId(), value);
}
@Override
public <T> T getDefault(CompositeOption<T> option) {
- return (T) getDefaultOptions().get(option);
+ return (T) getDefaultOptions().get(option.getId());
}
@Override
@@ -393,4 +425,13 @@ public class REIConfigScreen extends Screen implements ConfigAccess {
public CompositeOption<ModifierKeyCode> getFocusedKeycode() {
return this.focusedKeycodeOption;
}
+
+ public void setSearching(boolean searching) {
+ this.searching = searching;
+ this.init(this.minecraft, this.width, this.height);
+ }
+
+ public boolean isSearching() {
+ return searching;
+ }
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java
index 3ab77951c..072c73a8d 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java
@@ -27,30 +27,56 @@ import dev.architectury.utils.value.IntValue;
import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.api.client.gui.widgets.Widget;
import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
import me.shedaniel.rei.impl.client.gui.config.options.OptionCategory;
import me.shedaniel.rei.impl.client.gui.widget.ListWidget;
import me.shedaniel.rei.impl.client.gui.widget.ScrollableViewWidget;
import me.shedaniel.rei.impl.common.util.RectangleUtils;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
import java.util.List;
public class ConfigCategoriesListWidget {
public static Widget create(Rectangle bounds, List<OptionCategory> categories, IntValue selected) {
- WidgetWithBounds list = ListWidget.builderOf(RectangleUtils.inset(bounds, 3, 5), categories,
- (index, entry) -> ConfigCategoryEntryWidget.create(entry, bounds.width - 6))
+ final Mutable<WidgetWithBounds> list = new MutableObject<>(null);
+ list.setValue(ListWidget.builderOfWidgets(RectangleUtils.inset(bounds, 3, 5),
+ CollectionUtils.concatUnmodifiable(List.of(ConfigSearchWidget.create(() -> list.getValue() != null && list.getValue().getBounds().height + 6 > bounds.height ? bounds.width - 6 - 6 : bounds.width - 6)),
+ CollectionUtils.map(categories, entry -> ConfigCategoryEntryWidget.create(entry, bounds.width - 6))))
.gap(3)
- .isSelectable((index, entry) -> true)
- .selected(selected)
- .build();
- return ScrollableViewWidget.create(bounds, list.withPadding(0, 5), true);
+ .isSelectable((index, entry) -> index != 0)
+ .selected(new IntValue() {
+ @Override
+ public void accept(int value) {
+ selected.accept(value - 1);
+ }
+
+ @Override
+ public int getAsInt() {
+ return selected.getAsInt() + 1;
+ }
+ })
+ .build());
+ return ScrollableViewWidget.create(bounds, list.getValue().withPadding(0, 5), true);
}
public static Widget createTiny(Rectangle bounds, List<OptionCategory> categories, IntValue selected) {
- WidgetWithBounds list = ListWidget.builderOf(RectangleUtils.inset(bounds, (bounds.width - 6 - 16) / 2, 9), categories,
- (index, entry) -> ConfigCategoryEntryWidget.createTiny(entry))
+ WidgetWithBounds list = ListWidget.builderOfWidgets(RectangleUtils.inset(bounds, (bounds.width - 6 - 16) / 2, 9),
+ CollectionUtils.concatUnmodifiable(List.of(ConfigSearchWidget.createTiny()),
+ CollectionUtils.map(categories, ConfigCategoryEntryWidget::createTiny)))
.gap(7)
- .isSelectable((index, entry) -> true)
- .selected(selected)
+ .isSelectable((index, entry) -> index != 0)
+ .selected(new IntValue() {
+ @Override
+ public void accept(int value) {
+ selected.accept(value - 1);
+ }
+
+ @Override
+ public int getAsInt() {
+ return selected.getAsInt() + 1;
+ }
+ })
.build();
return ScrollableViewWidget.create(bounds, list.withPadding(0, 9), true);
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigEntriesListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigEntriesListWidget.java
index b1ecca932..906148b75 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigEntriesListWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigEntriesListWidget.java
@@ -37,7 +37,7 @@ import java.util.List;
public class ConfigEntriesListWidget {
public static Widget create(ConfigAccess access, Rectangle bounds, List<OptionGroup> groups) {
WidgetWithBounds list = ListWidget.builderOf(RectangleUtils.inset(bounds, 6, 6), groups,
- (index, entry) -> ConfigGroupWidget.create(access, entry, bounds.width - 12 - 6))
+ (index, entry) -> ConfigGroupWidget.create(access, entry, bounds.width - 12 - 6, false))
.gap(7)
.calculateTotalHeightDynamically(true)
.build();
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigGroupWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigGroupWidget.java
index 063596f1a..e41c233b2 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigGroupWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigGroupWidget.java
@@ -37,6 +37,7 @@ import me.shedaniel.rei.impl.client.gui.config.options.OptionGroup;
import me.shedaniel.rei.impl.client.gui.config.options.preview.AccessibilityDisplayPreviewer;
import me.shedaniel.rei.impl.client.gui.config.options.preview.InterfacePreviewer;
import me.shedaniel.rei.impl.client.gui.config.options.preview.TooltipPreviewer;
+import me.shedaniel.rei.impl.client.gui.text.TextTransformations;
import net.minecraft.client.gui.GuiComponent;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
@@ -59,13 +60,13 @@ public class ConfigGroupWidget {
SPECIAL_GROUPS.put(group, Pair.of(location, constructor));
}
- public static WidgetWithBounds create(ConfigAccess access, OptionGroup entry, int width) {
- WidgetWithBounds groupTitle = Widgets.createLabel(new Point(0, 3), entry.getGroupName().copy().withStyle(style -> style.withColor(0xFFC0C0C0).withUnderlined(true)))
+ public static WidgetWithBounds create(ConfigAccess access, OptionGroup entry, int width, boolean applyPreview) {
+ WidgetWithBounds groupTitle = Widgets.createLabel(new Point(0, 3), TextTransformations.highlightText(entry.getGroupName().copy(), entry.getGroupNameHighlight(), style -> style.withColor(0xFFC0C0C0).withUnderlined(true)))
.leftAligned()
.withPadding(0, 0, 0, 6);
WidgetWithBounds contents;
- if (SPECIAL_GROUPS.containsKey(entry)) {
+ if (applyPreview && SPECIAL_GROUPS.containsKey(entry)) {
Pair<PreviewLocation, SpecialGroupConstructor> pair = SPECIAL_GROUPS.get(entry);
PreviewLocation location = pair.getLeft();
int halfWidth = width * 6 / 10 - 2;
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigOptionWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigOptionWidget.java
index a449700a0..f2a47a332 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigOptionWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigOptionWidget.java
@@ -38,6 +38,7 @@ import me.shedaniel.rei.api.client.util.MatrixUtils;
import me.shedaniel.rei.impl.client.gui.config.ConfigAccess;
import me.shedaniel.rei.impl.client.gui.config.options.CompositeOption;
import me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils;
+import me.shedaniel.rei.impl.client.gui.text.TextTransformations;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.events.GuiEventListener;
@@ -57,17 +58,17 @@ public class ConfigOptionWidget {
int[] stableHeight = {12};
int[] height = {12};
Label fieldNameLabel;
- widgets.add(fieldNameLabel = Widgets.createLabel(new Point(0, 0), option.getName().copy().withStyle(style -> style.withColor(0xFFC0C0C0)))
+ widgets.add(fieldNameLabel = Widgets.createLabel(new Point(0, 0), TextTransformations.highlightText(option.getName().copy(), option.getOptionNameHighlight(), style -> style.withColor(0xFFC0C0C0)))
.leftAligned());
WidgetWithBounds optionValue = ConfigOptionValueWidget.create(access, option, width - 10 - fieldNameLabel.getBounds().width);
widgets.add(Widgets.withTranslate(optionValue, () -> Matrix4f.createTranslateMatrix(width - optionValue.getBounds().width - optionValue.getBounds().x, 0, 0)));
widgets.add(new WidgetWithBounds() {
final MutableComponent description = Util.make(() -> {
- MutableComponent description = option.getDescription().copy().withStyle(style -> style.withColor(0xFF757575));
+ MutableComponent description = option.getDescription().copy();
if (description.getString().endsWith(".desc")) {
return literal("");
} else {
- return description;
+ return TextTransformations.highlightText(description, option.getOptionDescriptionHighlight(), style -> style.withColor(0xFF757575));
}
});
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchListWidget.java
new file mode 100644
index 000000000..034203b88
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchListWidget.java
@@ -0,0 +1,127 @@
+package me.shedaniel.rei.impl.client.gui.config.components;
+
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.gui.widgets.TextField;
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds;
+import me.shedaniel.rei.api.client.gui.widgets.Widgets;
+import me.shedaniel.rei.impl.client.gui.config.ConfigAccess;
+import me.shedaniel.rei.impl.client.gui.config.options.CompositeOption;
+import me.shedaniel.rei.impl.client.gui.config.options.OptionCategory;
+import me.shedaniel.rei.impl.client.gui.config.options.OptionGroup;
+import me.shedaniel.rei.impl.client.gui.widget.ListWidget;
+import me.shedaniel.rei.impl.client.gui.widget.ScrollableViewWidget;
+import me.shedaniel.rei.impl.client.gui.widget.basewidgets.TextFieldWidget;
+import me.shedaniel.rei.impl.common.util.RectangleUtils;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ConfigSearchListWidget {
+ public static WidgetWithBounds create(ConfigAccess access, List<OptionCategory> categories, TextField textField, Rectangle bounds) {
+ Mutable<WidgetWithBounds> list = new MutableObject<>(null);
+ Consumer<String> responder = string -> {
+ Collection<ConfigSearchWidget.SearchResult> results = ConfigSearchWidget.matchResult(categories, string);
+ list.setValue(createList(access, results, string, bounds));
+ };
+ ((TextFieldWidget) textField).setResponder(responder);
+ responder.accept(textField.getText());
+
+ return ScrollableViewWidget.create(bounds, Widgets.delegateWithBounds(list::getValue), true);
+ }
+
+ private static WidgetWithBounds createList(ConfigAccess access, Collection<ConfigSearchWidget.SearchResult> results, String searchTerm, Rectangle bounds) {
+ return ListWidget.builderOfWidgets(RectangleUtils.inset(bounds, 6, 6),
+ collectResultWidgets(access, results, searchTerm, bounds))
+ .gap(7)
+ .calculateTotalHeightDynamically(true)
+ .build()
+ .withPadding(0, 5);
+ }
+
+ private static List<WidgetWithBounds> collectResultWidgets(ConfigAccess access, Collection<ConfigSearchWidget.SearchResult> results, String searchTerm, Rectangle bounds) {
+ List<ConfigSearchWidget.SearchResult> collapsedResults = new ArrayList<>();
+ for (ConfigSearchWidget.SearchResult result : results) {
+ if (result instanceof ConfigSearchWidget.IndividualResult individualResult) {
+ int lastMatchGroup = -1;
+ for (int i = 0; i < collapsedResults.size(); i++) {
+ ConfigSearchWidget.SearchResult prev = collapsedResults.get(i);
+ if (prev instanceof ConfigSearchWidget.IndividualResult prevInd && prevInd.group().getGroupName().getString().equals(individualResult.group().getGroupName().getString())) {
+ lastMatchGroup = i;
+ }
+ }
+ if (lastMatchGroup == -1) {
+ collapsedResults.add(result);
+ } else {
+ collapsedResults.add(lastMatchGroup + 1, result);
+ }
+ } else {
+ collapsedResults.add(result);
+ }
+ }
+
+ List<WidgetWithBounds> widgets = new ArrayList<>();
+ ConfigSearchWidget.SearchResult last = null;
+ List<CompositeOption<?>> merge = null;
+ for (ConfigSearchWidget.SearchResult result : collapsedResults) {
+ if (last instanceof ConfigSearchWidget.IndividualResult lastInd && result instanceof ConfigSearchWidget.IndividualResult currInd) {
+ if (lastInd.group().getGroupName().getString().equals(currInd.group().getGroupName().getString())) {
+ if (merge != null) {
+ merge.add(((ConfigSearchWidget.IndividualResult) currInd.decompose(searchTerm)).option());
+ } else {
+ merge = new ArrayList<>(List.of(((ConfigSearchWidget.IndividualResult) lastInd.decompose(searchTerm)).option(),
+ ((ConfigSearchWidget.IndividualResult) currInd.decompose(searchTerm)).option()));
+ }
+
+ last = result;
+ continue;
+ }
+ }
+
+ if (last != null) {
+ // Commit last
+ if (merge != null) {
+ OptionGroup group = ((ConfigSearchWidget.IndividualResult) last).group().copy();
+ group.getOptions().clear();
+ group.getOptions().addAll(merge);
+ merge = null;
+ widgets.add(createSearchResult(access, group, bounds.width - 12 - 6));
+ } else {
+ widgets.add(createSearchResult(access, last.decompose(searchTerm), bounds.width - 12 - 6));
+ }
+ }
+ last = result;
+ }
+
+ if (last != null) {
+ // Commit last
+ if (merge != null) {
+ OptionGroup group = ((ConfigSearchWidget.IndividualResult) last).group().copy();
+ group.getOptions().clear();
+ group.getOptions().addAll(merge);
+ widgets.add(createSearchResult(access, group, bounds.width - 12 - 6));
+ } else {
+ widgets.add(createSearchResult(access, last.decompose(searchTerm), bounds.width - 12 - 6));
+ }
+ }
+
+ return widgets;
+ }
+
+ private static WidgetWithBounds createSearchResult(ConfigAccess access, Object result, int width) {
+ if (result instanceof OptionCategory category) return Widgets.noOp();
+ if (result instanceof OptionGroup group) {
+ return ConfigGroupWidget.create(access, group, width, false);
+ }
+ if (result instanceof ConfigSearchWidget.IndividualResult individualResult) {
+ OptionGroup group = individualResult.group().copy();
+ group.getOptions().clear();
+ group.getOptions().add(individualResult.option());
+ return ConfigGroupWidget.create(access, group, width, false);
+ }
+ return Widgets.noOp();
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchWidget.java
new file mode 100644
index 000000000..e0012a575
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigSearchWidget.java
@@ -0,0 +1,373 @@
+package me.shedaniel.rei.impl.client.gui.config.components;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.shedaniel.math.Point;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.gui.widgets.Label;
+import me.shedaniel.rei.api.client.gui.widgets.Widget;
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds;
+import me.shedaniel.rei.api.client.gui.widgets.Widgets;
+import me.shedaniel.rei.impl.client.gui.config.REIConfigScreen;
+import me.shedaniel.rei.impl.client.gui.config.options.CompositeOption;
+import me.shedaniel.rei.impl.client.gui.config.options.OptionCategory;
+import me.shedaniel.rei.impl.client.gui.config.options.OptionGroup;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.resources.ResourceLocation;
+
+import java.util.*;
+import java.util.function.IntSupplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils.translatable;
+
+public class ConfigSearchWidget {
+ public static WidgetWithBounds create(IntSupplier width) {
+ Label label = Widgets.createLabel(new Point(21, 6), translatable("config.rei.texts.search_options"))
+ .leftAligned();
+ Font font = Minecraft.getInstance().font;
+ Rectangle bounds = new Rectangle(0, 0, label.getBounds().getMaxX(), 7 * 3);
+ return Widgets.concatWithBounds(
+ bounds,
+ new Widget() {
+ @Override
+ public void render(PoseStack poses, int mouseX, int mouseY, float delta) {
+ boolean hovering = new Rectangle(-1, -1, width.getAsInt() + 2, 21).contains(mouseX, mouseY);
+ for (Widget widget : List.of(Widgets.createFilledRectangle(new Rectangle(1, 1, width.getAsInt() - 2, 18), hovering ? 0x50FFFFFF : 0x25FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-1, -1, width.getAsInt() + 2, 1), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-1, 20, width.getAsInt() + 2, 1), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-1, 0, 1, 20), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(width.getAsInt(), 0, 1, 20), hovering ? 0x90FFFFFF : 0x45FFFFFF))) {
+ widget.render(poses, mouseX, mouseY, delta);
+ }
+ label.setColor(hovering ? 0xFFE1E1E1 : 0xFFC0C0C0);
+ }
+
+ @Override
+ public List<? extends GuiEventListener> children() {
+ return List.of();
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (new Rectangle(-1, -1, width.getAsInt() + 2, 21).contains(mouseX, mouseY)) {
+ Widgets.produceClickSound();
+ ((REIConfigScreen) Minecraft.getInstance().screen).setSearching(true);
+ return true;
+ }
+
+ return false;
+ }
+ },
+ Widgets.withTranslate(label, 0, 0.5, 0),
+ Widgets.createTexturedWidget(new ResourceLocation("roughlyenoughitems:textures/gui/config/search_options.png"), new Rectangle(3, 3, 16, 16), 0, 0, 1, 1, 1, 1)
+
+ );
+ }
+
+ public static WidgetWithBounds createTiny() {
+ Rectangle bounds = new Rectangle(0, 0, 16, 16);
+ return Widgets.withTooltip(Widgets.concatWithBounds(
+ bounds,
+ new Widget() {
+ @Override
+ public void render(PoseStack poses, int mouseX, int mouseY, float delta) {
+ boolean hovering = new Rectangle(-1, -1, 18, 18).contains(mouseX, mouseY);
+ poses.pushPose();
+ poses.translate(-0.5, -0.5, 0);
+ for (Widget widget : List.of(Widgets.createFilledRectangle(new Rectangle(-1, -1, 18, 18), hovering ? 0x50FFFFFF : 0x25FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-3, -3, 22, 1), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-3, 18, 22, 1), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(-3, -2, 1, 20), hovering ? 0x90FFFFFF : 0x45FFFFFF),
+ Widgets.createFilledRectangle(new Rectangle(18, -2, 1, 20), hovering ? 0x90FFFFFF : 0x45FFFFFF))) {
+ widget.render(poses, mouseX, mouseY, delta);
+ }
+ poses.popPose();
+ }
+
+ @Override
+ public List<? extends GuiEventListener> children() {
+ return List.of();
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (new Rectangle(-1, -1, 18, 18).contains(mouseX, mouseY)) {
+