diff --git a/build.gradle b/build.gradle index 462b29cfd..6543bbef6 100644 --- a/build.gradle +++ b/build.gradle @@ -101,6 +101,10 @@ test { repositories { mavenCentral() mavenLocal() + maven { + name "JEI" + url "https://maven.blamejared.com" + } maven { name "Cyclops Repo" url "https://maven.pkg.github.com/CyclopsMC/packages" @@ -136,6 +140,9 @@ dependencies { } } + // Optional, only used to hide JEI in focus mode + compileOnly "mezz.jei:jei-${project.minecraft_version}-neoforge-api:${project.jei_version}" + modLib "com.google.re2j:re2j:1.1" modLib ("io.netty:netty-codec-http:4.1.77.Final") { transitive = false diff --git a/gradle.properties b/gradle.properties index 50806d5ce..ff9723dfd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,4 +34,5 @@ org.gradle.caching=true graal_version=25.2.4 cyclopscore_version=1.25.5-643 integrateddynamics_version=1.24.1-1004 +jei_version=19.16.0.152 commoncapabilities_version=2.9.3-147 diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetTextArea.java b/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetTextArea.java index ef1564ed7..d49835143 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetTextArea.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetTextArea.java @@ -39,6 +39,7 @@ import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.Pair; +import org.joml.Vector2i; import org.cyclops.cyclopscore.client.gui.component.WidgetScrollBar; import org.cyclops.cyclopscore.client.gui.component.input.IInputListener; import org.cyclops.cyclopscore.helper.Helpers; @@ -82,7 +83,16 @@ public class WidgetTextArea extends EditBox implements GuiEventListener { private static final int POPUP_SELECTED = 0xFF505050; private static final int POPUP_TEXT = 0xFFE0E0E0; private static final int POPUP_DETAIL = 0xFF909090; - public static final int SCROLLBAR_WIDTH = 14; + public static final int SCROLLBAR_WIDTH = 7; + // Same height as the vanilla scroller, which the scrollbar's drag handling assumes + private static final int SCROLLBAR_HANDLE_HEIGHT = 15; + // Height of the arrow between a syntax error tooltip and its error + private static final int TOOLTIP_ARROW = 4; + // How far vanilla tooltip backgrounds extend beyond their contents + private static final int TOOLTIP_BORDER = 4; + // Colors of vanilla tooltips + private static final int TOOLTIP_BACKGROUND = 0xF0100010; + private static final int TOOLTIP_BORDER_COLOR = 0x505000FF; private final TextFieldHelperExtended textFieldHelper; private final Font font; @@ -164,9 +174,15 @@ public int getTotalRows() { @Override public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { - // Only show scrollbar if needed + // Only show scrollbar if needed, and draw a narrower handle than the vanilla scroller to leave more room for text if (this.needsScrollBars()) { - super.renderWidget(guiGraphics, mouseX, mouseY, partialTicks); + int x = this.getX(); + int y = this.getY() + (int) ((this.getHeight() - SCROLLBAR_HANDLE_HEIGHT - 2) * this.getCurrentScroll()); + int x2 = x + SCROLLBAR_WIDTH - 1; + int y2 = y + SCROLLBAR_HANDLE_HEIGHT; + guiGraphics.fill(x, y, x2, y2, Helpers.RGBAToInt(85, 85, 85, 255)); + guiGraphics.fill(x, y, x2 - 1, y2 - 1, Helpers.RGBAToInt(255, 255, 255, 255)); + guiGraphics.fill(x + 1, y + 1, x2 - 1, y2 - 1, Helpers.RGBAToInt(198, 198, 198, 255)); } } }; @@ -945,15 +961,21 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float // Render scrollbar if (this.scrollBar != null) { - this.scrollBar.render(guiGraphics, getX(), getY(), partialTicks); - - // Mark the position of the syntax error on the scrollbar, so it can be found when scrolled out of view + // Mark the position of the syntax error on the scrollbar, so it can be found when scrolled out of view. + // This is drawn below the handle, which only covers it when the error is roughly in view. if (this.syntaxError != null && displayCache.linesTotal > this.scrollBar.getVisibleRows()) { int errorRow = findLineFromPos(displayCache.lineStarts, TextEditing.lineOffset(this.getValue(), this.syntaxError.line())); int y = this.getY() + (int) ((long) errorRow * (this.getHeight() - 2) / displayCache.linesTotal); - int x = this.getX() + this.getWidth() - SCROLLBAR_WIDTH + 1; - guiGraphics.fill(x, y, x + SCROLLBAR_WIDTH - 2, y + 2, Helpers.RGBAToInt(220, 0, 0, 255)); + int x = this.getX() + this.getWidth() - SCROLLBAR_WIDTH; + guiGraphics.fill(x, y, x + SCROLLBAR_WIDTH - 1, y + 2, Helpers.RGBAToInt(220, 0, 0, 255)); + + // Show the message when hovering the mark + if (mouseX >= x && mouseX < x + SCROLLBAR_WIDTH - 1 && mouseY >= y - 1 && mouseY < y + 3 && Minecraft.getInstance().screen != null) { + Minecraft.getInstance().screen.setTooltipForNextRenderPass(this.font.split(Component.literal(this.syntaxError.message()), 200)); + } } + + this.scrollBar.render(guiGraphics, getX(), getY(), partialTicks); } } @@ -963,22 +985,64 @@ private void renderSyntaxError(GuiGraphics guiGraphics, DisplayCache displayCach int pos = Math.min(value.length(), TextEditing.lineOffset(value, this.syntaxError.line()) + Math.max(0, this.syntaxError.column() - 1)); int line = findLineFromPos(displayCache.lineStarts, pos); int row = line - this.firstRow; + int errorCenterX = -1; if (row >= 0 && row < this.getHeight() / ROW_HEIGHT) { int x = this.font.width(value.substring(displayCache.lineStarts[line], pos)); int width = pos < value.length() && value.charAt(pos) != '\n' ? this.font.width(value.substring(pos, pos + 1)) : 4; Pos2i screenPos = this.convertLocalToScreen(new Pos2i(x, row * ROW_HEIGHT)); guiGraphics.fill(screenPos.x, screenPos.y + ROW_HEIGHT - 2, screenPos.x + width, screenPos.y + ROW_HEIGHT - 1, Helpers.RGBAToInt(220, 0, 0, 255)); + errorCenterX = screenPos.x + width / 2; } - // Show the message when hovering the error line + // Show the message when hovering the error line, which may be wrapped over multiple rows + int top = Integer.MAX_VALUE; + int bottom = Integer.MIN_VALUE; + boolean hovering = false; for (LineInfo lineInfo : lines) { - if (lineInfo.lineNumber + 1 == this.syntaxError.line() && mouseX >= this.getX() && mouseX < this.getX() + this.getWidth() - && mouseY >= lineInfo.y && mouseY < lineInfo.y + ROW_HEIGHT && Minecraft.getInstance().screen != null) { - Minecraft.getInstance().screen.setTooltipForNextRenderPass(this.font.split(Component.literal(this.syntaxError.message()), 200)); + if (lineInfo.lineNumber + 1 == this.syntaxError.line()) { + top = Math.min(top, lineInfo.y); + bottom = Math.max(bottom, lineInfo.y + ROW_HEIGHT); + hovering |= mouseX >= this.getX() && mouseX < this.getX() + this.getWidth() + && mouseY >= lineInfo.y && mouseY < lineInfo.y + ROW_HEIGHT; + } + } + if (hovering && Minecraft.getInstance().screen != null) { + List message = this.font.split(Component.literal(this.syntaxError.message()), 200); + int screenHeight = Minecraft.getInstance().getWindow().getGuiScaledHeight(); + // Same height as vanilla tooltips of text lines + int tooltipHeight = message.size() * 10 - (message.size() == 1 ? 2 : 0); + + // Like in other editors, show the message below the line, or above it if there is no room + boolean below = bottom + TOOLTIP_ARROW + TOOLTIP_BORDER + tooltipHeight + TOOLTIP_BORDER <= screenHeight + || top - TOOLTIP_ARROW - TOOLTIP_BORDER - tooltipHeight - TOOLTIP_BORDER < 0; + int tooltipY = below ? bottom + TOOLTIP_ARROW + TOOLTIP_BORDER : top - TOOLTIP_ARROW - TOOLTIP_BORDER - tooltipHeight; + int anchorX = errorCenterX >= 0 ? errorCenterX - 12 : mouseX + 12; + Minecraft.getInstance().screen.setTooltipForNextRenderPass(message, (screenWidth, screenHeightIgnored, mouseXIgnored, mouseYIgnored, width, height) -> + new Vector2i(Math.max(TOOLTIP_BORDER, Math.min(anchorX, screenWidth - width - TOOLTIP_BORDER)), tooltipY), true); + + // Point at the column of the error + if (errorCenterX >= 0) { + this.renderTooltipArrow(guiGraphics, errorCenterX, below ? bottom : top - 1, below); } } } + /** + * Draw an arrow between a tooltip and the given position, in the style of vanilla tooltips. + */ + private void renderTooltipArrow(GuiGraphics guiGraphics, int x, int tipY, boolean pointUp) { + guiGraphics.pose().pushPose(); + // Same depth as tooltips + guiGraphics.pose().translate(0, 0, 400); + for (int i = 0; i < TOOLTIP_ARROW; i++) { + int y = pointUp ? tipY + i : tipY - i; + guiGraphics.fill(x - i, y, x + i + 1, y + 1, TOOLTIP_BACKGROUND); + guiGraphics.fill(x - i, y, x - i + 1, y + 1, TOOLTIP_BORDER_COLOR); + guiGraphics.fill(x + i, y, x + i + 1, y + 1, TOOLTIP_BORDER_COLOR); + } + guiGraphics.pose().popPose(); + } + private void renderCharBackground(GuiGraphics guiGraphics, DisplayCache displayCache, int pos, int color) { int line = findLineFromPos(displayCache.lineStarts, pos); int row = line - this.firstRow; diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java index 0f126149a..8dd09fa51 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java @@ -24,6 +24,7 @@ import net.minecraft.util.StringUtil; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; +import net.neoforged.fml.ModList; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Level; import org.cyclops.cyclopscore.client.gui.component.WidgetScrollBar; @@ -87,6 +88,8 @@ public class ContainerScreenTerminalScripting extends ContainerScreenExtended