diff --git a/build.gradle b/build.gradle index 6c3b0857..e4dea2da 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,7 @@ dependencies { compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")) compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}")) + runtimeOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}")) } test { diff --git a/src/main/java/codechicken/lib/compat/JEIPlugin.java b/src/main/java/codechicken/lib/compat/JEIPlugin.java deleted file mode 100644 index c9608464..00000000 --- a/src/main/java/codechicken/lib/compat/JEIPlugin.java +++ /dev/null @@ -1,37 +0,0 @@ -package codechicken.lib.compat; - -import codechicken.lib.CodeChickenLib; -import codechicken.lib.gui.modular.ModularGuiContainer; -import mezz.jei.api.IModPlugin; -import mezz.jei.api.JeiPlugin; -import mezz.jei.api.gui.handlers.IGuiContainerHandler; -import mezz.jei.api.registration.IGuiHandlerRegistration; -import net.minecraft.client.renderer.Rect2i; -import net.minecraft.resources.ResourceLocation; - -import java.util.List; - -/** - * Created by brandon3055 on 31/12/2023 - */ -@JeiPlugin -public class JEIPlugin implements IModPlugin { - private static final ResourceLocation ID = new ResourceLocation(CodeChickenLib.MOD_ID, "jei_plugin"); - - public JEIPlugin() {} - - @Override - public ResourceLocation getPluginUid() { - return ID; - } - - @Override - public void registerGuiHandlers(IGuiHandlerRegistration registration) { - registration.addGuiContainerHandler(ModularGuiContainer.class, new IGuiContainerHandler<>() { - @Override - public List getGuiExtraAreas(ModularGuiContainer screen) { - return screen.getModularGui().getJeiExclusions().map(e -> e.getRectangle().toRect2i()).toList(); - } - }); - } -} diff --git a/src/main/java/codechicken/lib/gui/modular/ModularGui.java b/src/main/java/codechicken/lib/gui/modular/ModularGui.java index b0a9d527..242ca4c0 100644 --- a/src/main/java/codechicken/lib/gui/modular/ModularGui.java +++ b/src/main/java/codechicken/lib/gui/modular/ModularGui.java @@ -15,18 +15,15 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.util.TriConsumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Consumer; -import java.util.stream.Stream; /** * The modular gui system is built around "Gui Elements" but those elements need to be rendered by a base parent element. That's what this class is. @@ -68,7 +65,7 @@ public class ModularGui implements GuiParent { private final List> preKeyPressListeners = new ArrayList<>(); private final List> postKeyPressListeners = new ArrayList<>(); - private final List> jeiExclusions = new ArrayList<>(); + private int jeiHighlightTime = 0; /** * @param provider The gui builder that will be used to construct this modular gui when the screen is initialized. @@ -208,6 +205,7 @@ public boolean vanillaSlotRendering() { * @param buffers BufferSource can be retried from {@link GuiGraphics} * @return A new {@link GuiRender} for the current render call. */ + @Deprecated //If you have the GuiGraphics, use GuiRender#convert to ensure the underlying PoseStack is carried over. That will ensure things like the JEI overlay will be rendered at a public GuiRender createRender(MultiBufferSource.BufferSource buffers) { return new GuiRender(mc, buffers); } @@ -216,7 +214,7 @@ public GuiRender createRender(MultiBufferSource.BufferSource buffers) { * Primary render method for ModularGui. The screen implementing ModularGui must call this in its render method. * Followed by the {@link #renderOverlay(GuiRender, float)} method to handle overlay rendering. * - * @param render A new gui render call should be constructed for each frame via {@link #createRender(MultiBufferSource.BufferSource)} + * @param render GuiRender instance converted from Minecraft's {@link GuiGraphics} via {@link GuiRender#convert(GuiGraphics)} */ public void render(GuiRender render, float partialTicks) { root.clearGeometryCache(); @@ -260,6 +258,7 @@ public void tick() { tickListeners.forEach(Runnable::run); root.tick(mouseX, mouseY); CursorHelper.setCursor(newCursor); + if (jeiHighlightTime > 0) jeiHighlightTime--; } /** @@ -524,26 +523,20 @@ public void setCursor(ResourceLocation cursor) { this.newCursor = cursor; } - /** - * Add an element to the list of jei exclusions. - * Use this for any elements that render outside the normal gui bounds. - * This will ensure JEI does not try to render on top of these elements. - */ - public void jeiExclude(GuiElement element) { - if (!jeiExclusions.contains(element)) { - jeiExclusions.add(element); - } + public List> getJeiExclusions() { + return root.addJeiExclusions(new ArrayList<>()); } - /** - * Remove an element from the list of jei exclusions. - */ - public void removeJEIExclude(GuiElement element) { - jeiExclusions.remove(element); + public List> getJeiDropTargets() { + return root.addJeiDropTargets(new ArrayList<>()); + } + + public void setJeiHighlightTime(int jeiHighlightTime) { + this.jeiHighlightTime = jeiHighlightTime; } - public FastStream> getJeiExclusions() { - return FastStream.of(jeiExclusions).filter(GuiElement::isEnabled); + public int getJeiHighlightTime() { + return jeiHighlightTime; } /** diff --git a/src/main/java/codechicken/lib/gui/modular/ModularGuiContainer.java b/src/main/java/codechicken/lib/gui/modular/ModularGuiContainer.java index 0063b6a6..8e74eba6 100644 --- a/src/main/java/codechicken/lib/gui/modular/ModularGuiContainer.java +++ b/src/main/java/codechicken/lib/gui/modular/ModularGuiContainer.java @@ -29,6 +29,10 @@ public class ModularGuiContainer extends AbstractContainerScreen implements ContainerScreenAccess { public final ModularGui modularGui; + /** + * Flag used to disable vanilla slot highlight rendering. + * */ + private boolean renderingSlots = false; public ModularGuiContainer(T containerMenu, Inventory inventory, ContainerGuiProvider provider) { super(containerMenu, inventory, Component.empty()); @@ -74,7 +78,7 @@ public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float if (modularGui.renderBackground()) { renderBackground(graphics); } - GuiRender render = modularGui.createRender(graphics.bufferSource()); + GuiRender render = GuiRender.convert(graphics);//modularGui.createRender(graphics.bufferSource()); modularGui.render(render, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks); @@ -125,7 +129,7 @@ protected boolean handleFloatingItemRender(GuiRender render, int mouseX, int mou protected boolean renderHoveredStackToolTip(GuiRender guiGraphics, int mouseX, int mouseY) { if (this.menu.getCarried().isEmpty() && this.hoveredSlot != null && this.hoveredSlot.hasItem()) { GuiElement handler = modularGui.getSlotHandler(hoveredSlot); - if (handler != null && handler.blockMouseOver(handler, mouseX, mouseY)) { + if (handler != null && (handler.blockMouseOver(handler, mouseX, mouseY) || !handler.isMouseOver())) { return false; } ItemStack itemStack = this.hoveredSlot.getItem(); @@ -191,7 +195,11 @@ protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) { @Override public void renderSlot(GuiGraphics guiGraphics, Slot slot) { - if (modularGui.vanillaSlotRendering()) super.renderSlot(guiGraphics, slot); + if (modularGui.vanillaSlotRendering()) { + super.renderSlot(guiGraphics, slot); + } else { + renderingSlots = true; + } } //Modular gui friendly version of the slot render @@ -240,8 +248,21 @@ public void renderSlot(GuiRender render, Slot slot) { } } + @Override + public boolean isHovering(Slot pSlot, double pMouseX, double pMouseY) { + boolean ret = super.isHovering(pSlot, pMouseX, pMouseY); + //Override the isHovering check before renderSlotHighlight is called. + if (ret && renderingSlots && pSlot.isActive()) { + //This breaks the default hoveredSlot assignment, so we need to handle that here. + hoveredSlot = pSlot; + return false; + } + return ret; + } + @Override //Disable vanilla title and inventory name rendering protected void renderLabels(GuiGraphics guiGraphics, int i, int j) { + renderingSlots = false; } @Override diff --git a/src/main/java/codechicken/lib/gui/modular/ModularGuiScreen.java b/src/main/java/codechicken/lib/gui/modular/ModularGuiScreen.java index 10fd1060..2e6251fd 100644 --- a/src/main/java/codechicken/lib/gui/modular/ModularGuiScreen.java +++ b/src/main/java/codechicken/lib/gui/modular/ModularGuiScreen.java @@ -67,7 +67,7 @@ public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float if (modularGui.renderBackground()) { renderBackground(graphics); } - GuiRender render = modularGui.createRender(graphics.bufferSource()); + GuiRender render = GuiRender.convert(graphics); modularGui.render(render, partialTicks); modularGui.renderOverlay(render, partialTicks); } diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiContextMenu.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiContextMenu.java index 77c2b343..704c701b 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiContextMenu.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiContextMenu.java @@ -3,13 +3,9 @@ import codechicken.lib.gui.modular.ModularGui; import codechicken.lib.gui.modular.lib.Constraints; import codechicken.lib.gui.modular.lib.geometry.GuiParent; -import net.covers1624.quack.collection.FastStream; import net.minecraft.network.chat.Component; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Supplier; @@ -26,14 +22,17 @@ public class GuiContextMenu extends GuiElement { private BiFunction, GuiButton> buttonBuilder = (menu, label) -> GuiButton.flatColourButton(menu, label, hover -> hover ? 0xFF475b6a : 0xFF151515).constrain(HEIGHT, literal(12)); - private final Map, Runnable> options = new HashMap<>(); + private final Map, Runnable> options = new LinkedHashMap<>(); private final Map, Supplier>> tooltips = new HashMap<>(); private final List buttons = new ArrayList<>(); private boolean closeOnItemClicked = true; private boolean closeOnOutsideClick = true; + private boolean actionOnClick = false; public GuiContextMenu(ModularGui gui) { super(gui.getRoot()); + jeiExclude(); + setOpaque(true); } public static GuiContextMenu tooltipStyleMenu(GuiParent parent) { @@ -52,6 +51,16 @@ public GuiContextMenu setCloseOnOutsideClick(boolean closeOnOutsideClick) { return this; } + /** + * By default, the option action fires on mouse button release (like pretty much any UI button in existence) + * Calling this will change that to action on mouse button press, (What minecraft uses for its buttons) + */ + public GuiContextMenu actionOnClick() { + this.actionOnClick = true; + rebuildButtons(); + return this; + } + /** * Only height should be constrained, with will be set automatically to accommodate the provided label. */ @@ -87,16 +96,20 @@ private void rebuildButtons() { //Menu options can be dynamic so the width constraint needs to be dynamic. //This is probably a little expensive, but its only while a context menu is open. - constrain(WIDTH, dynamic(() -> FastStream.of(options.keySet()).map(Supplier::get).intSum(font()::width) + 6D + 4D)); + constrain(WIDTH, dynamic(() -> options.keySet().stream().mapToInt(e -> font().width(e.get())).max().orElse(0) + 6D + 4D)); double height = 3; for (Supplier label : options.keySet()) { Runnable action = options.get(label); GuiButton button = buttonBuilder.apply(this, label) - .onPress(action) .constrain(TOP, relative(get(TOP), height)) .constrain(LEFT, relative(get(LEFT), 3)) .constrain(RIGHT, relative(get(RIGHT), -3)); + if (actionOnClick) { + button.onClick(action); + } else { + button.onPress(action); + } if (tooltips.containsKey(label)) { button.setTooltip(tooltips.get(label)); } @@ -111,10 +124,12 @@ private void rebuildButtons() { public boolean mouseClicked(double mouseX, double mouseY, int button, boolean consumed) { consumed = super.mouseClicked(mouseX, mouseY, button, consumed); if (isMouseOver() || consumed) { - if (consumed && closeOnItemClicked) { - close(); + if (actionOnClick) { + if (consumed && closeOnItemClicked) { + close(); + } + return true; } - return true; } else if (closeOnOutsideClick) { close(); return true; @@ -123,6 +138,20 @@ public boolean mouseClicked(double mouseX, double mouseY, int button, boolean co return consumed; } + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button, boolean consumed) { + consumed = super.mouseReleased(mouseX, mouseY, button, consumed); + if (isMouseOver() || consumed) { + if (!actionOnClick) { + if (consumed && closeOnItemClicked) { + close(); + } + return true; + } + } + return consumed; + } + public void close() { getParent().removeChild(this); } diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiElement.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiElement.java index 8942e51f..4ea5e1ac 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiElement.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiElement.java @@ -1,5 +1,6 @@ package codechicken.lib.gui.modular.elements; +import codechicken.lib.colour.ColourARGB; import com.google.common.collect.Lists; import com.mojang.blaze3d.vertex.PoseStack; import codechicken.lib.gui.modular.ModularGui; @@ -13,6 +14,7 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -68,6 +71,9 @@ public class GuiElement> extends ConstrainedGeometry private Supplier> toolTip = null; private Rectangle renderCull = Rectangle.create(Position.create(0, 0), () -> (double) screenWidth, () -> (double) screenHeight); + private Consumer jeiDropConsumer = null; + private boolean isJeiExcluded = false; + /** * @param parent parent {@link GuiParent}. */ @@ -220,6 +226,10 @@ public T setEnableToolTip(Supplier enableToolTip) { return SneakyUtils.unsafeCast(this); } + public boolean isTooltipEnabled() { + return enableToolTip.get(); + } + @Override public boolean blockMouseOver(GuiElement element, double mouseX, double mouseY) { return getParent().blockMouseOver(element, mouseX, mouseY); @@ -265,24 +275,6 @@ public String toString() { '}'; } - /** - * Add this element to the list of jei exclusions. - * Use this for any elements that render outside the normal gui bounds. - * This will ensure JEI does not try to render on top of these elements. - */ - public T jeiExclude() { - getModularGui().jeiExclude(this); - return SneakyUtils.unsafeCast(this); - } - - /** - * Remove this element from the list of jei exclusions. - */ - public T removeJEIExclude() { - getModularGui().removeJEIExclude(this); - return SneakyUtils.unsafeCast(this); - } - //=== Render / Update ===// /** @@ -422,8 +414,12 @@ public boolean renderOverlay(GuiRender render, double mouseX, double mouseY, flo return consumed || (showToolTip() && renderTooltip(render, mouseX, mouseY)); } - private boolean showToolTip() { - return isMouseOver() && enableToolTip.get() && hoverTime() >= getTooltipDelay(); + /** + * @return true if all conditions are bet for this element to be rendering its tool tip. + * Meaning mouse is over the element, hover time has elapsed and tooltip is enabled. + */ + public boolean showToolTip() { + return isMouseOver() && isTooltipEnabled() && hoverTime() >= getTooltipDelay(); } /** @@ -488,4 +484,72 @@ public T setTooltip(@Nullable Supplier> tooltip) { this.toolTip = tooltip; return SneakyUtils.unsafeCast(this); } + + //=== JEI Integration ===// + + /** + * Add this element to the list of jei exclusions. + * Use this for any elements that render outside the normal gui bounds. + * This will ensure JEI does not try to render on top of these elements. + */ + public T jeiExclude() { + isJeiExcluded = true; + return SneakyUtils.unsafeCast(this); + } + + public T setJeiExcluded(boolean jeiExcluded) { + isJeiExcluded = jeiExcluded; + return SneakyUtils.unsafeCast(this); + } + + public boolean isJeiExcluded() { + return isJeiExcluded; + } + + /** + * Allows you get notified when a player drags and drops an ItemStack from jai onto this element. + * If using the standard highlight then you should call this after adding any other required child elements to ensure the highlight is on top. + */ + public T setJeiDropTarget(Consumer onDrop, boolean installStandardHighlight) { + jeiDropConsumer = onDrop; + if (installStandardHighlight) { + GuiRectangle highlight = new GuiRectangle(this) + .setEnabled(() -> getModularGui().getJeiHighlightTime() > 0) + .fill(() -> ColourARGB.packARGB(0, 0xFF, 0, Math.min(getModularGui().getJeiHighlightTime() * 2, 0x50))); + Constraints.bind(highlight, this); + } + return SneakyUtils.unsafeCast(this); + } + + public Consumer getJeiDropConsumer() { + return jeiDropConsumer; + } + + public boolean isJeiDropTarget() { + return jeiDropConsumer != null; + } + + public List> addJeiExclusions(List> list) { + if (isJeiExcluded()) { + list.add(this); + } + for (GuiElement child : childElements) { + if (child.isEnabled()) { + child.addJeiExclusions(list); + } + } + return list; + } + + public List> addJeiDropTargets(List> list) { + if (isJeiDropTarget()) { + list.add(this); + } + for (GuiElement child : childElements) { + if (child.isEnabled()) { + child.addJeiDropTargets(list); + } + } + return list; + } } diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiEntityRenderer.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiEntityRenderer.java index ac09f9b3..f3e3b0db 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiEntityRenderer.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiEntityRenderer.java @@ -160,7 +160,9 @@ public GuiEntityRenderer setForce2dSize(boolean force2dSize) { @Override public double getBackgroundDepth() { Rectangle rect = getRectangle(); + if (invalidEntity || entity == null) return 0.01; float scale = (float) (force2dSize ? (Math.min(rect.height() / entity.getBbHeight(), rect.width() / entity.getBbWidth())) : rect.height() / entity.getBbHeight()); + if (Float.isInfinite(scale)) scale = 1; return scale * 2; } @@ -182,6 +184,11 @@ public void renderBackground(GuiRender render, double mouseX, double mouseY, flo } else { renderEntityInInventoryWithRotation(render, xPos, yPos, scale, rotation, living); } + } else { + Quaternionf quaternionf = new Quaternionf().rotateZ((float)Math.PI); + Quaternionf quaternionf1 = Axis.YP.rotationDegrees(rotation); + quaternionf.mul(quaternionf1); + renderEntityInInventory(render, xPos, yPos, scale, quaternionf, quaternionf1, entity); } } } catch (Throwable e) { @@ -242,7 +249,7 @@ public static void renderEntityInInventoryWithRotation(GuiRender render, double living.yHeadRot = f6; } - public static void renderEntityInInventory(GuiRender render, double pX, double pY, double pScale, Quaternionf quat, @Nullable Quaternionf pCameraOrientation, LivingEntity pEntity) { + public static void renderEntityInInventory(GuiRender render, double pX, double pY, double pScale, Quaternionf quat, @Nullable Quaternionf pCameraOrientation, Entity pEntity) { render.pose().pushPose(); render.pose().translate(pX, pY, 50.0D); render.pose().mulPoseMatrix((new Matrix4f()).scaling((float)pScale, (float)pScale, (float)(-pScale))); diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiItemStack.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiItemStack.java index 2ff85905..099d2c8a 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiItemStack.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiItemStack.java @@ -92,7 +92,7 @@ public double getStackSize() { @Override public double getBackgroundDepth() { - return getStackSize(); + return getStackSize() * 2; } @Override diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiList.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiList.java index 909d5d53..fe26fb75 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiList.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiList.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.function.BiFunction; +import java.util.function.Predicate; import static codechicken.lib.gui.modular.lib.geometry.Constraint.*; import static codechicken.lib.gui.modular.lib.geometry.GeoParam.*; @@ -35,6 +36,7 @@ public class GuiList extends GuiElement> { */ public boolean enableScissor = true; private double yScrollPos = 0; + private double lastWidth = 0; private double contentHeight = 0; private double itemSpacing = 1; private boolean rebuild = true; @@ -43,6 +45,7 @@ public class GuiList extends GuiElement> { private final List listContent = new ArrayList<>(); private final Map> elementMap = new HashMap<>(); private final LinkedList> visible = new LinkedList<>(); + private Predicate filter = e -> true; private BiFunction, E, ? extends GuiElement> displayBuilder = (parent, e) -> { GuiText text = new GuiText(parent, () -> Component.literal(String.valueOf(e))).setWrap(true); @@ -83,6 +86,11 @@ public GuiList setDisplayBuilder(BiFunction, E, ? extends GuiEleme return this; } + public GuiList setFilter(Predicate filter) { + this.filter = filter; + return this; + } + public GuiList setItemSpacing(double itemSpacing) { this.itemSpacing = itemSpacing; return this; @@ -127,6 +135,10 @@ public double hiddenSize() { @Override public void tick(double mouseX, double mouseY) { + if (lastWidth != xSize()) { + lastWidth = xSize(); + markDirty(); + } if (rebuild) { rebuildElements(); } @@ -138,6 +150,7 @@ public void rebuildElements() { elementMap.clear(); for (E item : listContent) { + if (!filter.test(item)) continue; GuiElement next = displayBuilder.apply(this, item); next.constrain(LEFT, match(get(LEFT))); next.constrain(RIGHT, match(get(RIGHT))); diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiSlots.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiSlots.java index 9ce861eb..aa21af09 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiSlots.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiSlots.java @@ -7,9 +7,12 @@ import codechicken.lib.gui.modular.lib.geometry.Constraint; import codechicken.lib.gui.modular.lib.geometry.GeoParam; import codechicken.lib.gui.modular.lib.geometry.GuiParent; +import codechicken.lib.gui.modular.lib.geometry.Position; import codechicken.lib.gui.modular.sprite.CCGuiTextures; import codechicken.lib.gui.modular.sprite.Material; +import net.covers1624.quack.collection.FastStream; import net.minecraft.world.inventory.Slot; +import org.apache.logging.log4j.util.TriConsumer; import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -38,9 +41,10 @@ public class GuiSlots extends GuiElement implements BackgroundRender { private final SlotGroup slots; private final ContainerScreenAccess screenAccess; - private Material slotTexture = CCGuiTextures.getUncached("widgets/slot"); - private Function slotIcons = slot -> null; - private Function highlightColour = slot -> 0x80ffffff; + private Function slotTexture = slot -> CCGuiTextures.getUncached("widgets/slot"); + private Function slotIcons = slot -> null; + private Function highlightColour = slot -> 0x80ffffff; + private TriConsumer slotOverlay = null; private int xSlotSpacing = 0; private int ySlotSpacing = 0; @@ -78,6 +82,7 @@ public GuiSlots(@NotNull GuiParent parent, ContainerScreenAccess screenAcc } updateSlots(parent.getModularGui().getRoot()); + setZStacking(false); } //=== Construction Helpers ===// @@ -126,7 +131,7 @@ public static PlayerWithArmor playerWithArmor(@NotNull GuiParent parent, Cont GuiSlots armor = new GuiSlots(container, screenAccess, armorSlots, 1) .setYSlotSpacing(groupSpacing / 3) - .setEmptyIcon(index -> slotIcons ? ARMOR_SLOTS[index] : null) + .setEmptyIconI(index -> slotIcons ? ARMOR_SLOTS[index] : null) .constrain(TOP, Constraint.midPoint(container.get(TOP), container.get(BOTTOM), height / -2D)) .constrain(LEFT, Constraint.midPoint(container.get(LEFT), container.get(RIGHT), width / -2D)); @@ -155,7 +160,7 @@ public static PlayerAll playerAllSlots(@NotNull GuiParent parent, ContainerSc GuiSlots armor = new GuiSlots(container, screenAccess, armorSlots, 1) .setYSlotSpacing(groupSpacing / 3) - .setEmptyIcon(index -> slotIcons ? ARMOR_SLOTS[index] : null) + .setEmptyIconI(index -> slotIcons ? ARMOR_SLOTS[index] : null) .constrain(TOP, Constraint.midPoint(container.get(TOP), container.get(BOTTOM), height / -2D)) .constrain(LEFT, Constraint.midPoint(container.get(LEFT), container.get(RIGHT), width / -2D)); @@ -168,7 +173,7 @@ public static PlayerAll playerAllSlots(@NotNull GuiParent parent, ContainerSc .constrain(LEFT, match(main.get(LEFT))); GuiSlots offHand = new GuiSlots(container, screenAccess, offhandSlots, 1) - .setEmptyIcon(index -> slotIcons ? OFF_HAND_SLOT : null) + .setEmptyIconI(index -> slotIcons ? OFF_HAND_SLOT : null) .constrain(TOP, match(bar.get(TOP))) .constrain(LEFT, relative(bar.get(RIGHT), groupSpacing)); @@ -181,10 +186,28 @@ public static PlayerAll playerAllSlots(@NotNull GuiParent parent, ContainerSc * Allows you to use a custom slot texture, The default is the standard vanilla slot. */ public GuiSlots setSlotTexture(Material slotTexture) { + this.slotTexture = e -> slotTexture; + return this; + } + + /** + * Allows you to use a custom per-slot slot textures, The default is the standard vanilla texture for all slots. + */ + public GuiSlots setSlotTexture(Function slotTexture) { this.slotTexture = slotTexture; return this; } + /** + * Allows you to use a custom per-slot slot textures, The default is the standard vanilla texture for all slots. + *

+ * Similar to {@link #setSlotTexture(Function)} except you are given the index of the slot within the {@link GuiSlots} element. + */ + public GuiSlots setSlotTextureI(Function slotTexture) { + this.slotTexture = slot -> slotTexture.apply(slots.indexOf(slot) - firstSlot); + return this; + } + /** * Sets a custom slot highlight colour (The highlight you get when your cursor is over a slot.) */ @@ -196,11 +219,22 @@ public GuiSlots setHighlightColour(int highlightColour) { * Allows you to set per-slot highlight colours, The integer passed to the function is the * index of the slot within the {@link SlotGroup} */ - public GuiSlots setHighlightColour(Function highlightColour) { + public GuiSlots setHighlightColour(Function highlightColour) { this.highlightColour = highlightColour; return this; } + /** + * Allows you to set per-slot highlight colours, The integer passed to the function is the + * index of the slot within the {@link SlotGroup} + *

+ * Similar to {@link #setHighlightColour(Function)} except you are given the index of the slot within the {@link GuiSlots} element. + */ + public GuiSlots setHighlightColourI(Function highlightColour) { + this.highlightColour = slot -> highlightColour.apply(slots.indexOf(slot) - firstSlot); + return this; + } + /** * Applies a single empty slot icon to all slots. * Recommended texture size is 16x16 @@ -215,11 +249,48 @@ public GuiSlots setEmptyIcon(Material texture) { * * @param slotIcons A function that is given the slot index within the {@link SlotGroup}, and should return a material or null. */ - public GuiSlots setEmptyIcon(Function slotIcons) { + public GuiSlots setEmptyIcon(Function slotIcons) { this.slotIcons = slotIcons; return this; } + /** + * Allows you to provide a texture to be rendered in each slot when the slot is empty. + * Recommended texture size is 16x16 + *

+ * Similar to {@link #setEmptyIcon(Function)} except you are given the index of the slot within the {@link GuiSlots} element. + * + * @param slotIcons A function that is given the slot index within the {@link SlotGroup}, and should return a material or null. + */ + public GuiSlots setEmptyIconI(Function slotIcons) { + this.slotIcons = slot -> slotIcons.apply(slots.indexOf(slot) - firstSlot); + return this; + } + + /** + * Allows you to attach an overlay renderer that will get called for each slot, after all slots have been rendered. + * This can be used to render pretty much anything you want to overtop the slot. + * + * @param slotOverlay Render callback providing the slot, screen position of the slot (top-left corner) and the active GuiRender. + */ + public GuiSlots setSlotOverlay(TriConsumer slotOverlay) { + this.slotOverlay = slotOverlay; + return this; + } + + /** + * Allows you to attach an overlay renderer that will get called for each slot, after all slots have been rendered. + * This can be used to render pretty much anything you want to overtop the slot. + *

+ * Similar to {@link #setSlotOverlay(TriConsumer)} except you are given the index of the slot within the {@link GuiSlots} element. + * + * @param slotOverlay Render callback providing the slot, screen position of the slot (top-left corner) and the active GuiRender. + */ + public GuiSlots setSlotOverlayI(TriConsumer slotOverlay) { + this.slotOverlay = (slot, position, render) -> slotOverlay.accept(slots.indexOf(slot) - firstSlot, position, render); + return this; + } + public GuiSlots setXSlotSpacing(int xSlotSpacing) { this.xSlotSpacing = xSlotSpacing; return this; @@ -275,7 +346,7 @@ public void renderBackground(GuiRender render, double mouseX, double mouseY, flo for (int index = 0; index < slotCount; index++) { Slot slot = slots.getSlot(index + firstSlot); - render.texRect(slotTexture, slot.x + root.xMin() - 1, slot.y + root.yMin() - 1, 18, 18); + render.texRect(slotTexture.apply(slot), slot.x + root.xMin() - 1, slot.y + root.yMin() - 1, 18, 18); } render.pose().translate(0, 0, 0.4); @@ -284,29 +355,50 @@ public void renderBackground(GuiRender render, double mouseX, double mouseY, flo Slot slot = slots.getSlot(index + firstSlot); if (!slot.isActive()) continue; if (!slot.hasItem()) { - Material icon = slotIcons.apply(index + firstSlot); + Material icon = slotIcons.apply(slot); if (icon != null) { render.texRect(icon, slot.x + root.xMin(), slot.y + root.yMin(), 16, 16); } } screenAccess.renderSlot(render, slot); - if (GuiRender.isInRect(slot.x + root.xMin(), slot.y + root.yMin(), 16, 16, mouseX, mouseY) && !blockMouseOver(this, mouseX, mouseY)) { + if (GuiRender.isInRect(slot.x + root.xMin(), slot.y + root.yMin(), 16, 16, mouseX, mouseY) && !blockMouseOver(this, mouseX, mouseY) && isMouseOver()) { highlightSlot = slot; } } + render.pose().translate(0, 0, getBackgroundDepth() - 0.8); + + if (slotOverlay != null) { + for (int index = 0; index < slotCount; index++) { + Slot slot = slots.getSlot(index + firstSlot); + if (!slot.isActive()) continue; + slotOverlay.accept(slot, Position.create(slot.x + root.xMin(), slot.y + root.yMin()), render); + } + } + if (highlightSlot != null) { - render.pose().translate(0, 0, getBackgroundDepth() - 0.8); - render.rect(highlightSlot.x + root.xMin(), highlightSlot.y + root.yMin(), 16, 16, highlightColour.apply(slots.indexOf(highlightSlot))); + render.rect(highlightSlot.x + root.xMin(), highlightSlot.y + root.yMin(), 16, 16, highlightColour.apply(highlightSlot)); } render.pose().popPose(); } - public record Player(GuiElement container, GuiSlots main, GuiSlots hotBar) {} + public record Player(GuiElement container, GuiSlots main, GuiSlots hotBar) { + public FastStream stream() { + return FastStream.of(main, hotBar); + } + } - public record PlayerWithArmor(GuiElement container, GuiSlots main, GuiSlots hotBar, GuiSlots armor) {} + public record PlayerWithArmor(GuiElement container, GuiSlots main, GuiSlots hotBar, GuiSlots armor) { + public FastStream stream() { + return FastStream.of(main, hotBar, armor); + } + } - public record PlayerAll(GuiElement container, GuiSlots main, GuiSlots hotBar, GuiSlots armor, GuiSlots offHand) {} + public record PlayerAll(GuiElement container, GuiSlots main, GuiSlots hotBar, GuiSlots armor, GuiSlots offHand) { + public FastStream stream() { + return FastStream.of(main, hotBar, armor, offHand); + } + } } diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiText.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiText.java index 2776c2d5..f553d660 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiText.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiText.java @@ -204,7 +204,7 @@ public GuiText setRotatePoint(Position rotatePoint) { @Override public double getForegroundDepth() { - return 0.035; + return 0.05; } @Override diff --git a/src/main/java/codechicken/lib/gui/modular/elements/GuiTextField.java b/src/main/java/codechicken/lib/gui/modular/elements/GuiTextField.java index 680b918c..c9c0a62f 100644 --- a/src/main/java/codechicken/lib/gui/modular/elements/GuiTextField.java +++ b/src/main/java/codechicken/lib/gui/modular/elements/GuiTextField.java @@ -62,6 +62,7 @@ public class GuiTextField extends GuiElement implements Background private Supplier suggestion = null; private Supplier suggestionColour = () -> 0x7f7f80; + private Supplier suggestionShadow = () -> true; private Predicate filter = Objects::nonNull; private BiFunction formatter = (string, pos) -> FormattedCharSequence.forward(string, Style.EMPTY); @@ -162,8 +163,24 @@ public GuiTextField setSuggestion(@Nullable Supplier suggestion) { /** * Set the colour of the suggestion text. */ - public void setSuggestionColour(Supplier suggestionColour) { + public GuiTextField setSuggestionColour(Supplier suggestionColour) { this.suggestionColour = suggestionColour; + return this; + } + + public GuiTextField setSuggestionColour(int suggestionColour) { + this.suggestionColour = () -> suggestionColour; + return this; + } + + public GuiTextField setSuggestionShadow(Supplier suggestionShadow) { + this.suggestionShadow = suggestionShadow; + return this; + } + + public GuiTextField setSuggestionShadow(boolean suggestionShadow) { + this.suggestionShadow = () -> suggestionShadow; + return this; } /** @@ -630,7 +647,7 @@ public void renderBackground(GuiRender render, double mouseX, double mouseY, flo } if (suggestion != null && value.isEmpty()) { - render.drawString(suggestion.get(), (float) (k1 - 1), (float) drawY, suggestionColour.get(), shadow.get()); + render.drawString(suggestion.get(), (float) (k1 - 1), (float) drawY, suggestionColour.get(), suggestionShadow.get()); } if (cursorBlink) { diff --git a/src/main/java/codechicken/lib/gui/modular/lib/TooltipHandler.java b/src/main/java/codechicken/lib/gui/modular/lib/TooltipHandler.java index 7bc1bb20..46ebc188 100644 --- a/src/main/java/codechicken/lib/gui/modular/lib/TooltipHandler.java +++ b/src/main/java/codechicken/lib/gui/modular/lib/TooltipHandler.java @@ -99,7 +99,8 @@ default boolean renderTooltip(GuiRender render, double mouseX, double mouseY) { if (supplier == null) return false; List list = supplier.get(); if (list.isEmpty()) return false; - render.componentTooltip(list, mouseX, mouseY); + //Run all components though split to account for newline characters in translations + render.renderTooltip(list.stream().flatMap(component -> render.font().split(component, Integer.MAX_VALUE).stream()).toList(), mouseX, mouseY); return true; } } diff --git a/src/main/java/codechicken/lib/gui/modular/lib/geometry/ConstraintImpl.java b/src/main/java/codechicken/lib/gui/modular/lib/geometry/ConstraintImpl.java index 646abe93..207fb8ae 100644 --- a/src/main/java/codechicken/lib/gui/modular/lib/geometry/ConstraintImpl.java +++ b/src/main/java/codechicken/lib/gui/modular/lib/geometry/ConstraintImpl.java @@ -160,7 +160,7 @@ public Between(GeoRef start, GeoRef end, double pos) { @Override protected double getImpl() { - return start.get() + (end.get() - start.get()) * getPos(); + return start.get() + ((end.get() - start.get()) * getPos()); } public double getPos() { @@ -202,7 +202,7 @@ public BetweenDynamic(GeoRef start, GeoRef end, Supplier pos) { @Override protected double getImpl() { - return start.get() + (end.get() - start.get()) * getPos(); + return start.get() + ((end.get() - start.get()) * getPos()); } public double getPos() { diff --git a/src/main/java/codechicken/lib/internal/compat/JEIPlugin.java b/src/main/java/codechicken/lib/internal/compat/JEIPlugin.java new file mode 100644 index 00000000..3a0bea86 --- /dev/null +++ b/src/main/java/codechicken/lib/internal/compat/JEIPlugin.java @@ -0,0 +1,87 @@ +package codechicken.lib.internal.compat; + +import codechicken.lib.CodeChickenLib; +import codechicken.lib.gui.modular.ModularGui; +import codechicken.lib.gui.modular.ModularGuiContainer; +import codechicken.lib.gui.modular.elements.GuiElement; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.handlers.IGhostIngredientHandler; +import mezz.jei.api.gui.handlers.IGuiContainerHandler; +import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.registration.IGuiHandlerRegistration; +import net.covers1624.quack.collection.FastStream; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by brandon3055 on 31/12/2023 + */ +@JeiPlugin +public class JEIPlugin implements IModPlugin { + private static final ResourceLocation ID = new ResourceLocation(CodeChickenLib.MOD_ID, "jei_plugin"); + + public JEIPlugin() { + } + + @Override + public ResourceLocation getPluginUid() { + return ID; + } + + @Override + public void registerGuiHandlers(IGuiHandlerRegistration registration) { + registration.addGuiContainerHandler(ModularGuiContainer.class, new IGuiContainerHandler<>() { + @Override + public List getGuiExtraAreas(ModularGuiContainer screen) { + return FastStream.of(screen.getModularGui().getJeiExclusions()).map(e -> e.getRectangle().toRect2i()).toList(); + } + }); + registration.addGhostIngredientHandler(ModularGuiContainer.class, new IngredientDropHandler()); + } + + private static class IngredientDropHandler implements IGhostIngredientHandler { + private ModularGui gui; + private boolean highlight = true; + + @Override + public List> getTargetsTyped(ModularGuiContainer screen, ITypedIngredient ingredient, boolean doStart) { + gui = screen.getModularGui(); + gui.setJeiHighlightTime(doStart ? 60 * 20 : 3 * 20); + highlight = !doStart; + if (!doStart) return Collections.emptyList(); + List> targets = new ArrayList<>(); + ingredient.getIngredient(VanillaTypes.ITEM_STACK).ifPresent(stack -> gui.getJeiDropTargets().forEach(e -> targets.add(new DropTarget<>(e)))); + return targets; + } + + @Override + public void onComplete() { + highlight = true; + gui.setJeiHighlightTime(0); + } + + @Override + public boolean shouldHighlightTargets() { + return highlight; + } + } + + private record DropTarget(GuiElement element) implements IGhostIngredientHandler.Target { + @Override + public Rect2i getArea() { + return element.getRectangle().toRect2i(); + } + + @Override + public void accept(I ingredient) { + element.getJeiDropConsumer().accept((ItemStack) ingredient); + } + } +} diff --git a/src/main/java/codechicken/lib/inventory/container/modular/ModularSlot.java b/src/main/java/codechicken/lib/inventory/container/modular/ModularSlot.java index c652f2cd..75dcc995 100644 --- a/src/main/java/codechicken/lib/inventory/container/modular/ModularSlot.java +++ b/src/main/java/codechicken/lib/inventory/container/modular/ModularSlot.java @@ -106,7 +106,7 @@ public ModularSlot setEnabled(boolean enabled) { @Override public boolean mayPlace(ItemStack itemStack) { - return canPlace && validator.test(itemStack) && (!checkContainer || container.canPlaceItem(getContainerSlot(), itemStack)); + return canPlace && validator.test(itemStack) && (!checkContainer || getItemHandler().isItemValid(getContainerSlot(), itemStack)); } @Override diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 0cd041ee..3c773fef 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -67,6 +67,7 @@ public com.mojang.blaze3d.shaders.Program$Type m_85571_()I # getGlType public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_280092_(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/inventory/Slot;)V # renderSlot public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_280211_(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V # renderFloatingItem public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97744_(DD)Lnet/minecraft/world/inventory/Slot; # findSlot +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97774_(Lnet/minecraft/world/inventory/Slot;DD)Z # isHovering public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97818_()V # recalculateQuickCraftRemaining public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97706_ # clickedSlot public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97711_ # draggingItem