aboutsummaryrefslogtreecommitdiff
path: root/runtime-frontend/overlay/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'runtime-frontend/overlay/src/main/java')
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/AbstractScreenOverlay.java330
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/InternalOverlayBounds.java50
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/LateRenderable.java30
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/ScreenOverlayImpl.java215
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/ScreenOverlayProviderImpl.java34
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/TooltipQueueImpl.java57
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/dragging/DraggingContextImpl.java318
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/entries/EntryListProvider.java38
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/entries/EntryListWidget.java44
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/entries/FavoritesListProvider.java38
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/entries/FavoritesListWidget.java41
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/hints/InputMethodWatcher.java172
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/AbstractMenuEntry.java79
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/Menu.java212
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/MenuAccessImpl.java155
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/entries/EmptyMenuEntry.java58
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/entries/SeparatorMenuEntry.java53
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/entries/SubMenuEntry.java154
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/entries/TextMenuEntry.java70
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/entries/ToggleMenuEntry.java143
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/menu/provider/OverlayMenuEntryProvider.java43
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/widgets/DisplayTooltipComponent.java82
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/widgets/DisplayedEntryWidget.java248
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/widgets/OverlayWidgetProvider.java47
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/widgets/RegionRenderingDebugger.java80
-rw-r--r--runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/widgets/ScaleIndicatorWidget.java72
26 files changed, 2863 insertions, 0 deletions
diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/AbstractScreenOverlay.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/AbstractScreenOverlay.java
new file mode 100644
index 000000000..d1c3a5f22
--- /dev/null
+++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/AbstractScreenOverlay.java
@@ -0,0 +1,330 @@
+/*
+ * 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.gui.overlay;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.PoseStack;
+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.config.ConfigObject;
+import me.shedaniel.rei.api.client.favorites.FavoriteEntry;
+import me.shedaniel.rei.api.client.gui.drag.DraggingContext;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentProvider;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentVisitor;
+import me.shedaniel.rei.api.client.gui.widgets.Tooltip;
+import me.shedaniel.rei.api.client.gui.widgets.TooltipQueue;
+import me.shedaniel.rei.api.client.gui.widgets.Widget;
+import me.shedaniel.rei.api.client.overlay.ScreenOverlay;
+import me.shedaniel.rei.api.client.registry.screen.ClickArea;
+import me.shedaniel.rei.api.client.registry.screen.OverlayDecider;
+import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry;
+import me.shedaniel.rei.api.client.view.ViewSearchBuilder;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.impl.client.ClientInternals;
+import me.shedaniel.rei.impl.client.gui.overlay.dragging.DraggingContextImpl;
+import me.shedaniel.rei.impl.client.gui.menu.MenuAccess;
+import me.shedaniel.rei.impl.client.gui.overlay.menu.MenuAccessImpl;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.InteractionResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractScreenOverlay extends ScreenOverlay {
+ private final List<Widget> widgets = new ArrayList<>();
+ private final DraggingContextImpl draggingContext = new DraggingContextImpl();
+ private final MenuAccessImpl menuAccess = new MenuAccessImpl();
+ private final Rectangle bounds = new Rectangle();
+ private boolean shouldReload = false;
+ private boolean shouldReloadSearch = false;
+
+ @Override
+ public void queueReloadOverlay() {
+ shouldReload = true;
+ }
+
+ @Override
+ public void queueReloadSearch() {
+ shouldReloadSearch = true;
+ }
+
+ @Override
+ public boolean isOverlayReloadQueued() {
+ return shouldReload;
+ }
+
+ @Override
+ public boolean isSearchReloadQueued() {
+ return shouldReloadSearch || shouldReload;
+ }
+
+ @Override
+ public DraggingContext<?> getDraggingContext() {
+ return draggingContext;
+ }
+
+ @Override
+ public List<Widget> children() {
+ return widgets;
+ }
+
+ @Override
+ public Rectangle getBounds() {
+ return bounds;
+ }
+
+ protected boolean hasSpace() {
+ return !this.bounds.isEmpty();
+ }
+
+ public void init() {
+ this.draggingContext.set(DraggableComponentProvider.from(ScreenRegistry.getInstance()::getDraggableComponentProviders),
+ DraggableComponentVisitor.from(ScreenRegistry.getInstance()::getDraggableComponentVisitors));
+
+ this.shouldReload = false;
+ this.shouldReloadSearch = false;
+ this.bounds.setBounds(InternalOverlayBounds.calculateOverlayBounds());
+ this.widgets.clear();
+ this.widgets.add(draggingContext);
+ }
+
+ @Override
+ public void render(PoseStack matrices, int mouseX, int mouseY, float delta) {
+ if (shouldReload || !InternalOverlayBounds.calculateOverlayBounds().equals(bounds)) {
+ init();
+ updateSearch();
+ } else {
+ for (OverlayDecider decider : ScreenRegistry.getInstance().getDeciders(minecraft.screen)) {
+ if (decider != null && decider.shouldRecalculateArea(ConfigObject.getInstance().getDisplayPanelLocation(), bounds)) {
+ init();
+ break;
+ }
+ }
+ }
+ if (shouldReloadSearch) {
+ shouldReloadSearch = false;
+ updateSearch();
+ }
+ if (!hasSpace()) return;
+ RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
+ this.renderWidgets(matrices, mouseX, mouseY, delta);
+ if (ConfigObject.getInstance().areClickableRecipeArrowsEnabled()) {
+ Screen screen = Minecraft.getInstance().screen;
+ ClickArea.ClickAreaContext<Screen> context = ClickArea.ClickAreaContext.of(screen, new Point(mouseX, mouseY));
+ List<Component> clickAreaTooltips = ScreenRegistry.getInstance().getClickAreaTooltips((Class<Screen>) screen.getClass(), context);
+ if (clickAreaTooltips != null && !clickAreaTooltips.isEmpty()) {
+ Tooltip.create(clickAreaTooltips).queue();
+ }
+ }
+ }
+
+ public void lateRender(PoseStack matrices, int mouseX, int mouseY, float delta) {
+ if (REIRuntime.getInstance().isOverlayVisible() && hasSpace()) {
+ getSearchField().asWidget().render(matrices, mouseX, mouseY, delta);
+ for (Widget widget : widgets) {
+ if (widget instanceof LateRenderable && widget != menuAccess.widget())
+ widget.render(matrices, mouseX, mouseY, delta);
+ }
+ matrices.pushPose();
+ matrices.translate(0, 0, 500);
+ menuAccess.lateRender(matrices, mouseX, mouseY, delta);
+ matrices.popPose();
+ }
+
+ Tooltip tooltip = TooltipQueue.getInstance().get();
+ if (tooltip != null) {
+ renderTooltip(matrices, tooltip);
+ }
+
+ TooltipQueue.getInstance().clear();
+ if (REIRuntime.getInstance().isOverlayVisible()) {
+ menuAccess.afterRender();
+ }
+ }
+
+ @Override
+ public void renderTooltip(PoseStack matrices, Tooltip tooltip) {
+ ClientInternals.getTooltipRenderer().renderTooltip(minecraft.screen, matrices, tooltip, tooltip.getX(), tooltip.getY());
+ }
+
+ public void renderWidgets(PoseStack matrices, int mouseX, int mouseY, float delta) {
+ if (!REIRuntime.getInstance().isOverlayVisible())
+ return;
+ for (Widget widget : widgets) {
+ if (!(widget instanceof LateRenderable))
+ widget.render(matrices, mouseX, mouseY, delta);
+ }
+ }
+
+ protected abstract void updateSearch();
+
+ public MenuAccess menuAccess() {
+ return menuAccess;
+ }
+
+ public boolean isInside(double mouseX, double mouseY) {
+ return bounds.contains(mouseX, mouseY) && isNotInExclusionZones(mouseX, mouseY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ boolean visible = REIRuntime.getInstance().isOverlayVisible();
+ if (!hasSpace()) return false;
+ if (ConfigObject.getInstance().getHideKeybind().matchesMouse(button)) {
+ REIRuntime.getInstance().toggleOverlayVisible();
+ return REIRuntime.getInstance().isOverlayVisible();
+ }
+ EntryStack<?> stack = ScreenRegistry.getInstance().getFocusedStack(Minecraft.getInstance().screen, PointHelper.ofMouse());
+ if (stack != null && !stack.isEmpty()) {
+ stack = stack.copy();
+ if (ConfigObject.getInstance().getRecipeKeybind().matchesMouse(button)) {
+ return ViewSearchBuilder.builder().addRecipesFor(stack).open();
+ } else if (ConfigObject.getInstance().getUsageKeybind().matchesMouse(button)) {
+ return ViewSearchBuilder.builder().addUsagesFor(stack).open();
+ } else if (visible && ConfigObject.getInstance().getFavoriteKeyCode().matchesMouse(button)) {
+ FavoriteEntry favoriteEntry = FavoriteEntry.fromEntryStack(stack);
+ ConfigObject.getInstance().getFavoriteEntries().add(favoriteEntry);
+ return true;
+ }
+ }
+ if (visible) {
+ Widget menuWidget = menuAccess.widget();
+ if (menuWidget != null && menuWidget.mouseClicked(mouseX, mouseY, button)) {
+ this.setFocused(menuWidget);
+ if (button == 0)
+ this.setDragging(true);
+ return true;
+ }
+ }
+ if (ConfigObject.getInstance().areClickableRecipeArrowsEnabled()) {
+ Screen screen = Minecraft.getInstance().screen;
+ ClickArea.ClickAreaContext<Screen> context = ClickArea.ClickAreaContext.of(screen, new Point(mouseX, mouseY));
+ if (ScreenRegistry.getInstance().executeClickArea((Class<Screen>) screen.getClass(), context)) {
+ return true;
+ }
+ }
+ if (!visible) {
+ return false;
+ }
+ for (GuiEventListener element : widgets) {
+ if (element != menuAccess.widget() && element.mouseClicked(mouseX, mouseY, button)) {
+ this.setFocused(element);
+ if (button == 0)
+ this.setDragging(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
+ if (!REIRuntime.getInstance().isOverlayVisible())
+ return false;
+ if (menuAccess.mouseScrolled(mouseX, mouseY, amount))
+ return true;
+ for (Widget widget : widgets)
+ if (widget != menuAccess.widget()
+ && widget.mouseScrolled(mouseX, mouseY, amount))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ if (!REIRuntime.getInstance().isOverlayVisible())
+ return false;
+ if (!hasSpace()) return false;
+ return (this.getFocused() != null && this.isDragging() && button == 0) && this.getFocused().mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (!hasSpace()) return false;
+ if (REIRuntime.getInstance().isOverlayVisible()) {
+ for (GuiEventListener listener : widgets)
+ if (listener.keyPressed(keyCode, scanCode, modifiers))
+ return true;
+ }
+ if (ConfigObject.getInstance().getHideKeybind().matchesKey(keyCode, scanCode)) {
+ REIRuntime.getInstance().toggleOverlayVisible();
+ return true;
+ }
+ EntryStack<?> stack = ScreenRegistry.getInstance().getFocusedStack(Minecraft.getInstance().screen, PointHelper.ofMouse());
+ if (stack != null && !stack.isEmpty()) {
+ stack = stack.copy();
+ if (ConfigObject.getInstance().getRecipeKeybind().matchesKey(keyCode, scanCode)) {
+ return ViewSearchBuilder.builder().addRecipesFor(stack).open();
+ } else if (ConfigObject.getInstance().getUsageKeybind().matchesKey(keyCode, scanCode)) {
+ return ViewSearchBuilder.builder().addUsagesFor(stack).open();
+ } else if (ConfigObject.getInstance().getFavoriteKeyCode().matchesKey(keyCode, scanCode)) {
+ FavoriteEntry favoriteEntry = FavoriteEntry.fromEntryStack(stack);
+ ConfigObject.getInstance().getFavoriteEntries().add(favoriteEntry);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
+ if (!hasSpace()) return false;
+ if (REIRuntime.getInstance().isOverlayVisible()) {
+ for (GuiEventListener listener : widgets)
+ if (listener == getFocused() && listener.keyReleased(keyCode, scanCode, modifiers))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean charTyped(char character, int modifiers) {
+ if (!REIRuntime.getInstance().isOverlayVisible())
+ return false;
+ if (!hasSpace()) return false;
+ for (GuiEventListener listener : widgets)
+ if (listener.charTyped(character, modifiers))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean isNotInExclusionZones(double mouseX, double mouseY) {
+ for (OverlayDecider decider : ScreenRegistry.getInstance().getDeciders(Minecraft.getInstance().screen)) {
+ InteractionResult in = decider.isInZone(mouseX, mouseY);
+ if (in != InteractionResult.PASS)
+ return in == InteractionResult.SUCCESS;
+ }
+ return true;
+ }
+
+ public boolean isInside(Point point) {
+ return isInside(point.getX(), point.getY());
+ }
+}
diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/InternalOverlayBounds.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/InternalOverlayBounds.java
new file mode 100644
index 000000000..bf98c858f
--- /dev/null
+++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/InternalOverlayBounds.java
@@ -0,0 +1,50 @@
+/*
+ * 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.gui.overlay;
+
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.config.ConfigObject;
+import me.shedaniel.rei.api.client.gui.config.DisplayPanelLocation;
+import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry;
+import me.shedaniel.rei.impl.client.util.InternalEntryBounds;
+import net.minecraft.client.Minecraft;
+
+public class InternalOverlayBounds {
+ static Rectangle calculateOverlayBounds() {
+ Rectangle bounds = ScreenRegistry.getInstance().getOverlayBounds(ConfigObject.getInstance().getDisplayPanelLocation(), Minecraft.getInstance().screen);
+
+ int widthReduction = (int) Math.round(bounds.width * (1 - ConfigObject.getInstance().getHorizontalEntriesBoundariesPercentage()));
+ if (ConfigObject.getInstance().getDisplayPanelLocation() == DisplayPanelLocation.RIGHT)
+ bounds.x += widthReduction;
+ bounds.width -= widthReduction;
+ int maxWidth = (int) Math.ceil(InternalEntryBounds.entrySize() * ConfigObject.getInstance().getHorizontalEntriesBoundariesColumns() + InternalEntryBounds.entrySize() * 0.75);
+ if (bounds.width > maxWidth) {
+ if (ConfigObject.getInstance().getDisplayPanelLocation() == DisplayPanelLocation.RIGHT)
+ bounds.x += bounds.width - maxWidth;
+ bounds.width = maxWidth;
+ }
+
+ return bounds;
+ }
+}
diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/LateRenderable.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/LateRenderable.java
new file mode 100644
index 000000000..6bfdb0e9a
--- /dev/null
+++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/LateRenderable.java
@@ -0,0 +1,30 @@
+/*
+ * 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.gui.overlay;
+
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public interface LateRenderable {
+}
diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/ScreenOverlayImpl.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/ScreenOverlayImpl.java
new file mode 100644
index 000000000..6245a1730
--- /dev/null
+++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/overlay/ScreenOverlayImpl.java
@@ -0,0 +1,215 @@
+/*
+ * 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.gui.overlay;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.REIRuntime;
+import me.shedaniel.rei.api.client.config.ConfigObject;
+import me.shedaniel.rei.api.client.gui.widgets.DelegateWidget;
+import me.shedaniel.rei.api.client.gui.widgets.TextField;
+import me.shedaniel.rei.api.client.gui.widgets.Widget;
+import me.shedaniel.rei.api.client.gui.widgets.Widgets;
+import me.shedaniel.rei.api.client.overlay.OverlayListWidget;
+import me.shedaniel.rei.api.client.search.SearchFilter;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.impl.client.gui.overlay.entries.EntryListProvider;
+import me.shedaniel.rei.impl.client.gui.overlay.entries.EntryListWidget;
+import me.shedaniel.rei.impl.client.gui.overlay.entries.FavoritesListProvider;
+import me.shedaniel.rei.impl.client.gui.overlay.entries.FavoritesListWidget;
+import me.shedaniel.rei.impl.client.gui.overlay.widgets.OverlayWidgetProvider;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BooleanSupplier;
+
+@ApiStatus.Internal
+public final class ScreenOverlayImpl extends AbstractScreenOverlay {
+ private EntryListWidget entryListWidget = null;
+ private FavoritesListWidget favoritesListWidget = null;
+ private TextField searchField = null;
+ private BooleanSupplier isHighlighted = null;
+
+ @Override
+ public void init() {
+ super.init();
+
+ this.searchField = null;
+ this.isHighlighted = null;
+
+ FavoritesListWidget favoritesListWidget = getFavoritesListNullable();
+
+ if (favoritesListWidget != null) {
+ this.children().add(favoritesListWidget.asWidget());
+ }
+
+ EntryListWidget entryListWidget = getEntryList();
+ entryListWidget.initBounds(this.getBounds());
+ this.children().add(entryListWidget.asWidget());
+ entryListWidget.init(this);
+
+ for (OverlayWidgetProvider provider : OverlayWidgetProvider.PROVIDERS) {
+ provider.provide(this, menuAccess(), new OverlayWidgetProvider.WidgetSink() {
+ @Override
+ public void accept(Widget widget) {
+ ScreenOverlayImpl.this.children().add(widget);
+ }
+
+ @Override
+ public void acceptLateRendered(Widget widget) {
+ accept(new LateRenderableWidget(widget));
+ }
+
+ @Override
+ public void acceptTextField(TextField textField, BooleanSupplier isHighlighted) {
+ ScreenOverlayImpl.this.searchField = textField;
+ ScreenOverlayImpl.this.isHighlighted = isHighlighted;
+ }
+ });
+ }
+
+ if (this.searchField != null) {
+ this.children().add(new LateRenderableWidget(this.searchField.asWidget()));
+ this.searchField.setResponder(s -> entryListWidget.initSearch(s, false));
+ entryListWidget.initSearch(searchField.getText(), true);
+ } else {
+ InternalLogger.getInstance().warn("Search Field is not found! This might cause problems!");
+ entryListWidget.initSearch("", true);
+ }
+ }
+
+ private static class LateRenderableWidget extends DelegateWidget implements LateRenderable {
+ private LateRenderableWidget(Widget widget) {
+ super(widget);
+ }
+ }
+
+ @Override
+ protected void updateSearch() {
+ getEntryList().initSearch(getSearchField().getText(), true);
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (super.keyPressed(keyCode, scanCode, modifiers)) return true;
+ if (!hasSpace()) return false;
+ if (!REIRuntime.getInstance().isOverlayVisible()) return false;
+ if (ConfigObject.getInstance().getFocusSearchFieldKeybind().matchesKey(keyCode, scanCode)) {
+ getSearchField().setFocusedFromKey(true, InputConstants.getKey(keyCode, scanCode));
+ setFocused(getSearchField().asWidget());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (super.mouseClicked(mouseX, mouseY, button)) return true;
+ boolean visible = REIRuntime.getInstance().isOverlayVisible();
+ if (!hasSpace() || !visible) return false;
+ if (ConfigObject.getInstance().getFocusSearchFieldKeybind().matchesMouse(button)) {
+ getSearchField().setFocusedFromKey(true, InputConstants.Type.MOUSE.getOrCreate(button));
+ setFocused(getSearchField().asWidget());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setFocused(@Nullable GuiEventListener focused) {
+ super.setFocused(focused);
+ getSearchField().setFocused(focused == getSearchField().asWidget());
+ }
+
+ @Override
+ public EntryListWidget getEntryList() {
+ EntryListWidget current = null;
+ for (EntryListProvider provider : EntryListProvider.PROVIDERS) {
+ current = provider.getEntryList();
+ if (current != null) break;
+ }
+ if (current == null) throw new IllegalStateException("No Entry List available!");
+ if (current != entryListWidget) {
+ entryListWidget = current;
+ current.initBounds(Objects.requireNonNullElse(getBounds(), new Rectangle()));
+ current.initSearch(getSearchField().getText(), true);
+ }
+ return entryListWidget;
+ }
+
+ @Override
+ public Optional<OverlayListWidget> getFavoritesList() {
+ return Optional.ofNullable(getFavoritesListNullable());
+ }
+
+ private FavoritesListWidget getFavoritesListNullable() {
+ if (ConfigObject.getInstance().isFavoritesEnabled()) {
+ FavoritesListWidget current = null;
+ for (FavoritesListProvider provider : FavoritesListProvider.PROVIDERS) {
+ current = provider.getFavoritesList();
+ if (current != null) break;</