From 18c15b13bcf199bb1e4dec0c6d7177af3866a0b8 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Mon, 9 Mar 2026 09:13:29 +0200 Subject: [PATCH] GuiScriptInterface is now pannable! Core functionality added to GuiNPCInterface, which means technically any child of that can be pannable --- .../client/gui/script/GuiScriptInterface.java | 38 +++- .../npcs/client/gui/util/GuiNPCInterface.java | 180 +++++++++++++++--- 2 files changed, 189 insertions(+), 29 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java index 24de353cd..9607a7b2e 100644 --- a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java +++ b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java @@ -102,6 +102,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb this.drawDefaultBackground = true; this.closeOnEsc = true; this.xSize = 420; + this.isPannableGUI = true; this.setBackground("menubg.png"); } @@ -142,6 +143,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb public void initGui() { // ==================== BASE LAYOUT CALCULATION ==================== + isPanning = false; this.ySize = (int) ((double) this.xSize * 0.56D); if ((double) this.ySize > (double) this.height * 0.95D) { this.ySize = (int) ((double) this.height * 0.95D); @@ -334,6 +336,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb } this.addScroll(scroll); } + + computePanBounds(editorX, editorY, editorWidth, editorHeight); } /** * Initialize the settings tab layout (console view). @@ -376,6 +380,12 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb // Language button requires options to be available this.getButton(103).enabled = languageOptions.size() > 0; } + + int consoleX = this.guiLeft + 4 + yoffset; + int consoleY = this.guiTop + 6 + yoffset; + int consoleW = this.xSize - 160 - yoffset; + int consoleH = (int) ((float) this.ySize * 0.92F) - yoffset * 2; + computePanBounds(consoleX, consoleY, consoleW, consoleH); } public GuiScriptInterface setDimensions(int x, int y) { @@ -393,21 +403,35 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb protected ScriptContext getScriptContext() { return handler.getContext(); } - - // ==================== MOUSE HANDLING ==================== @Override - public void mouseClicked(int mouseX, int mouseY, int mouseButton) { - // Check if click is within autocomplete menu bounds and consume it if so + protected boolean isPannableArea(int mx, int my) { + if (activeTab > 0 && fullscreenButton.isMouseOver(mx, my)) { + return false; + } + + GuiScriptTextArea activeArea = getActiveScriptArea(); + if (activeArea != null && activeArea.isPointOnAutocompleteMenu(mx, my)) { + return false; + } + + return super.isPannableArea(mx, my); + } + + @Override + public void mouseClicked(int i, int j, int k) { + int adjX = panAdjustedX(i); + int adjY = panAdjustedY(j); + GuiScriptTextArea activeArea = getActiveScriptArea(); boolean isOverAutocomplete = activeArea != null - && activeArea.isPointOnAutocompleteMenu(mouseX, mouseY); + && activeArea.isPointOnAutocompleteMenu(adjX, adjY); if (isOverAutocomplete) { - activeArea.mouseClicked(mouseX, mouseY, mouseButton); + activeArea.mouseClicked(adjX, adjY, k); return; } - super.mouseClicked(mouseX, mouseY, mouseButton); + super.mouseClicked(i, j, k); } public String previousHookClicked = ""; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java index 7d8b7f072..a1aba094c 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java @@ -61,6 +61,29 @@ public abstract class GuiNPCInterface extends GuiScreen { public float bgScaleZ = 1; public int bgTextureHeight = 256; // Actual content height in texture (for bottom border stitching) + // ==================== VIEWPORT PANNING ==================== + /** + * If true, this GUI supports viewport panning via click-drag on empty areas. + * Subclasses set this to true and call computePanBounds() to enable. + */ + protected boolean isPannableGUI = false; + protected boolean isPanning = false; + protected double panStartX, panStartY; + + /** + * Cumulative pan offset in scaled GUI pixels. + * Positive values = viewport shifted right/down (content appears to move left/up). + */ + public double currentPanX, currentPanY; + + public static double activePanX, activePanY; + + protected double minPanX, maxPanX, minPanY, maxPanY; + protected int panViewportX, panViewportY, panViewportWidth, panViewportHeight; + + /** Pan-adjusted mouse coordinates, updated every frame in drawScreen for hit-testing panned components. */ + protected int panAdjMouseX, panAdjMouseY; + public GuiNPCInterface(EntityNPCInterface npc) { this.player = Minecraft.getMinecraft().thePlayer; this.npc = npc; @@ -161,29 +184,38 @@ public abstract class GuiNPCInterface extends GuiScreen { if (subgui != null) subgui.mouseClicked(i, j, k); else { - mouseEvent(i, j, k); - vanillaMouseClicked(i, j, k); + int adjX = isPannableGUI ? panAdjustedX(i) : i; + int adjY = isPannableGUI ? panAdjustedY(j) : j; + + if (isPannableGUI && k == 0 && isPannableArea(adjX, adjY)) { + isPanning = true; + panStartX = i; + panStartY = j; + return; + } + + mouseEvent(adjX, adjY, k); + vanillaMouseClicked(adjX, adjY, k); for (GuiScrollWindow guiScrollableComponent : scrollWindows.values()) { - guiScrollableComponent.mouseClicked(i, j, k); + guiScrollableComponent.mouseClicked(adjX, adjY, k); } if (k == 0) { for (GuiCustomScroll scroll : new ArrayList(scrolls.values())) { - scroll.mouseClicked(i, j, k); + scroll.mouseClicked(adjX, adjY, k); } } - // Process diagram mouse clicks for (GuiDiagram diagram : diagrams.values()) { - if (diagram.isWithin(i, j)) { - if (diagram.mouseClicked(i, j, k)) + if (diagram.isWithin(adjX, adjY)) { + if (diagram.mouseClicked(adjX, adjY, k)) return; } } for (GuiNpcTextField tf : new ArrayList(textfields.values())) if (tf.enabled) - tf.mouseClicked(i, j, k); + tf.mouseClicked(adjX, adjY, k); } } @@ -225,7 +257,12 @@ public abstract class GuiNPCInterface extends GuiScreen { subgui.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); return; } - super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + if (isPannableGUI && isPanning) { + return; + } + int adjX = isPannableGUI ? panAdjustedX(mouseX) : mouseX; + int adjY = isPannableGUI ? panAdjustedY(mouseY) : mouseY; + super.mouseClickMove(adjX, adjY, clickedMouseButton, timeSinceLastClick); } @Override @@ -234,12 +271,72 @@ public abstract class GuiNPCInterface extends GuiScreen { subgui.mouseMovedOrUp(mouseX, mouseY, state); return; } - super.mouseMovedOrUp(mouseX, mouseY, state); + if (isPannableGUI && state == 0 && isPanning) { + isPanning = false; + panStartX = 0; + panStartY = 0; + return; + } + int adjX = isPannableGUI ? panAdjustedX(mouseX) : mouseX; + int adjY = isPannableGUI ? panAdjustedY(mouseY) : mouseY; + super.mouseMovedOrUp(adjX, adjY, state); } public void mouseEvent(int i, int j, int k) { } + // ==================== VIEWPORT PANNING METHODS ==================== + + protected boolean isPannableArea(int mx, int my) { + for (Object obj : this.buttonList) { + GuiButton btn = (GuiButton) obj; + if (btn.visible && mx >= btn.xPosition && mx < btn.xPosition + btn.width + && my >= btn.yPosition && my < btn.yPosition + btn.height) { + return false; + } + } + + for (GuiCustomScroll scroll : scrolls.values()) { + if (scroll.visible && scroll.isMouseOver(mx, my)) { + return false; + } + } + + for (GuiNpcTextField tf : textfields.values()) { + if (tf.enabled && mx >= tf.xPosition && mx < tf.xPosition + tf.width + && my >= tf.yPosition && my < tf.yPosition + tf.height) { + return false; + } + } + + return true; + } + + protected void computePanBounds(int vpX, int vpY, int vpW, int vpH) { + this.panViewportX = vpX; + this.panViewportY = vpY; + this.panViewportWidth = vpW; + this.panViewportHeight = vpH; + + int panRange = Math.max(this.width, this.height) / 2; + this.minPanX = -panRange; + this.maxPanX = panRange; + this.minPanY = -panRange; + this.maxPanY = panRange; + } + + protected int panAdjustedX(int rawMouseX) { + return rawMouseX + (int) currentPanX; + } + + protected int panAdjustedY(int rawMouseY) { + return rawMouseY + (int) currentPanY; + } + + public boolean hasPanOffset() { + return isPannableGUI && (currentPanX != 0 || currentPanY != 0); + } + @Override protected void actionPerformed(GuiButton guibutton) { if (subgui != null) @@ -377,9 +474,43 @@ public abstract class GuiNPCInterface extends GuiScreen { public void drawScreen(int i, int j, float f) { mouseX = i; mouseY = j; + + boolean panning = hasPanOffset(); + int drawX = panning ? panAdjustedX(i) : i; + int drawY = panning ? panAdjustedY(j) : j; + panAdjMouseX = drawX; + panAdjMouseY = drawY; + + // Smooth pan: update pan offset every frame using raw mouse delta + if (isPannableGUI && isPanning && Mouse.isButtonDown(0)) { + double deltaX = i - panStartX; + double deltaY = j - panStartY; + + currentPanX = Math.max(minPanX, Math.min(maxPanX, currentPanX - deltaX)); + currentPanY = Math.max(minPanY, Math.min(maxPanY, currentPanY - deltaY)); + + panStartX = i; + panStartY = j; + + panning = hasPanOffset(); + drawX = panning ? panAdjustedX(i) : i; + drawY = panning ? panAdjustedY(j) : j; + panAdjMouseX = drawX; + panAdjMouseY = drawY; + } else if (isPanning && !Mouse.isButtonDown(0)) { + isPanning = false; + } + if (drawDefaultBackground && subgui == null) drawDefaultBackground(); + if (panning) { + GL11.glPushMatrix(); + GL11.glTranslated(-currentPanX, -currentPanY, 0); + activePanX = currentPanX; + activePanY = currentPanY; + } + if (background != null && mc.renderEngine != null) { drawBackground(); } @@ -390,40 +521,45 @@ public abstract class GuiNPCInterface extends GuiScreen { label.drawLabel(this, fontRendererObj); for (GuiCustomScroll scroll : scrolls.values()) { scroll.updateSubGUI(subGui); - scroll.drawScreen(i, j, f, !subGui && scroll.isMouseOver(i, j) ? Mouse.getDWheel() : 0); + scroll.drawScreen(drawX, drawY, f, !subGui && scroll.isMouseOver(drawX, drawY) ? Mouse.getDWheel() : 0); } - // Draw scrollable windows. for (GuiScrollWindow guiScrollableComponent : scrollWindows.values()) { - guiScrollableComponent.drawScreen(i, j, f, !subGui && guiScrollableComponent.isMouseOver(i, j) ? Mouse.getDWheel() : 0); + guiScrollableComponent.drawScreen(drawX, drawY, f, !subGui && guiScrollableComponent.isMouseOver(drawX, drawY) ? Mouse.getDWheel() : 0); } for (GuiDiagram diagram : diagrams.values()) { - diagram.drawDiagram(i, j, subGui); + diagram.drawDiagram(drawX, drawY, subGui); } - super.drawScreen(i, j, f); + super.drawScreen(drawX, drawY, f); for (GuiCustomScroll scroll : scrolls.values()) if (scroll.hoverableText) { - scroll.drawHover(i, j); + scroll.drawHover(drawX, drawY); } for (GuiNpcButton button : buttons.values()) { button.updateSubGUI(subGui); if (!button.hoverableText.isEmpty()) { - button.drawHover(i, j, subGui); + button.drawHover(drawX, drawY, subGui); } } for (GuiNpcTextField tf : textfields.values()) { - tf.drawTextBox(i, j); + tf.drawTextBox(drawX, drawY); if (tf.hasHoverText()) - tf.drawHover(i, j, subGui); + tf.drawHover(drawX, drawY, subGui); } for(GuiScrollWindow guiScrollableComponent : scrollWindows.values()) { - if (guiScrollableComponent.isMouseOver(mouseX, mouseY)) { - guiScrollableComponent.drawHoverTexts(mouseX, mouseY); + if (guiScrollableComponent.isMouseOver(drawX, drawY)) { + guiScrollableComponent.drawHoverTexts(drawX, drawY); } } for (GuiScreen gui : extra.values()) - gui.drawScreen(i, j, f); + gui.drawScreen(drawX, drawY, f); + + if (panning) { + GL11.glPopMatrix(); + activePanX = 0; + activePanY = 0; + } if (subgui != null) { subgui.drawScreen(i, j, f);