From c537a09ad1a66f262233c24227c0fd39ba4f02ff Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Thu, 5 Mar 2026 01:17:22 +0200 Subject: [PATCH] Made Hover Info resizable via handle at bottom right --- .../client/gui/util/GuiScriptTextArea.java | 63 ++++++++++++------- .../script/interpreter/hover/HoverState.java | 49 +++++++++++++++ .../interpreter/hover/TokenHoverRenderer.java | 40 +++++++++++- 3 files changed, 129 insertions(+), 23 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiScriptTextArea.java b/src/main/java/noppes/npcs/client/gui/util/GuiScriptTextArea.java index 4b9675b1a..9eed161ac 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiScriptTextArea.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiScriptTextArea.java @@ -668,6 +668,14 @@ public class GuiScriptTextArea extends GuiNpcTextField { hoverState.releaseTooltipDrag(); } } + // Handle tooltip panel resize (update each frame; release when button lifted) + if (hoverState.isResizingTooltip()) { + if (Mouse.isButtonDown(0)) { + hoverState.updateTooltipResize(xMouse, yMouse); + } else { + hoverState.releaseTooltipResize(); + } + } // Update scroll animation scroll.initializeIfNeeded(scroll.getScrolledLine()); @@ -1208,8 +1216,13 @@ public class GuiScriptTextArea extends GuiNpcTextField { hoverState.setLastMousePosition(xMouse, yMouse); return; } - // Tooltip was dragged to a custom position — keep it pinned until an explicit click dismisses it - if (hoverState.hasOverriddenPosition()) { + // Keep tooltip alive while resizing (mouse may be outside the panel bounds) + if (hoverState.isResizingTooltip()) { + hoverState.setLastMousePosition(xMouse, yMouse); + return; + } + // Tooltip has been dragged or resized — keep it pinned until an explicit outside click + if (hoverState.hasOverriddenPosition() || hoverState.hasOverriddenSize()) { hoverState.setLastMousePosition(xMouse, yMouse); return; } @@ -1749,6 +1762,12 @@ public class GuiScriptTextArea extends GuiNpcTextField { } } + // Dismiss pinned/resized tooltip on Escape + if (i == Keyboard.KEY_ESCAPE && hoverState.isTooltipVisible() + && (hoverState.hasOverriddenPosition() || hoverState.hasOverriddenSize())) { + hoverState.clearHover(); + return true; + } // Ignore if any global keys bound to this code are currently pressed if (KEYS.hasMatchingKeyPressed(i)) return false; @@ -2693,7 +2712,9 @@ public class GuiScriptTextArea extends GuiNpcTextField { } public boolean closeOnEsc(){ - return !KEYS_OVERLAY.isVisible() && !searchBar.isVisible() && !goToLineDialog.isVisible() && !renameHandler.isActive() && !autocompleteManager.isVisible(); + return !KEYS_OVERLAY.isVisible() && !searchBar.isVisible() && !goToLineDialog.isVisible() + && !renameHandler.isActive() && !autocompleteManager.isVisible() + && !(hoverState.isTooltipVisible() && (hoverState.hasOverriddenPosition() || hoverState.hasOverriddenSize())); } // ==================== KEYBOARD MODIFIERS ==================== @@ -2889,6 +2910,24 @@ public class GuiScriptTextArea extends GuiNpcTextField { // ==================== MOUSE HANDLING ==================== public void mouseClicked(int xMouse, int yMouse, int mouseButton) { + // HIGHEST PRIORITY: tooltip panel absorbs all clicks — no click-through to panels beneath it + if (hoverState.isTooltipVisible() && hoverState.isMouseOverTooltipPanel(xMouse, yMouse)) { + if (mouseButton == 0) { + if (hoverState.isMouseOverResizeHandle(xMouse, yMouse)) { + hoverState.startTooltipResize(xMouse, yMouse); + } else if (hoverState.isMouseOverScrollbarThumb(xMouse, yMouse)) { + hoverState.startScrollbarDrag(yMouse); + } else { + hoverState.startTooltipDrag(xMouse, yMouse); + } + } + return; // consume — nothing below receives this click + } + // M1 outside a dragged/resized tooltip — dismiss it and fall through to editor + if (mouseButton == 0 && (hoverState.hasOverriddenPosition() || hoverState.hasOverriddenSize()) + && !hoverState.isMouseOverTooltipPanel(xMouse, yMouse)) { + hoverState.clearHover(); + } // Check autocomplete menu clicks first if (autocompleteManager.isVisible() && autocompleteManager.mouseClicked(xMouse, yMouse, mouseButton)) { return; @@ -2922,24 +2961,6 @@ public class GuiScriptTextArea extends GuiNpcTextField { if (KEYS_OVERLAY.mouseClicked(xMouse, yMouse, mouseButton)) return; - // Check tooltip scrollbar click (highest priority — before text interaction) - if (mouseButton == 0 && hoverState.isTooltipVisible() - && hoverState.isMouseOverScrollbarThumb(xMouse, yMouse)) { - hoverState.startScrollbarDrag(yMouse); - return; - } - // Check tooltip panel drag — M1 anywhere on the panel body (not the scrollbar thumb) - if (mouseButton == 0 && hoverState.isTooltipVisible() - && hoverState.isMouseOverTooltipPanel(xMouse, yMouse) - && !hoverState.isMouseOverScrollbarThumb(xMouse, yMouse)) { - hoverState.startTooltipDrag(xMouse, yMouse); - return; - } - // M1 outside a drag-pinned tooltip — dismiss it and let the click fall through to the editor - if (mouseButton == 0 && hoverState.hasOverriddenPosition() - && !hoverState.isMouseOverTooltipPanel(xMouse, yMouse)) { - hoverState.clearHover(); - } // Determine whether click occurred inside the text area bounds this.active = xMouse >= this.x && xMouse < this.x + this.width && yMouse >= this.y && yMouse < this.y + this.height; if (this.active) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java index 54dfef473..7618561c5 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java @@ -65,6 +65,16 @@ public class HoverState { private boolean tooltipPositionOverridden; private int overriddenTooltipX, overriddenTooltipY; + /** Tooltip panel resize state */ + private boolean isResizingTooltip; + private int resizeInitMouseX, resizeInitMouseY; + private int resizeInitW, resizeInitH; + private boolean tooltipSizeOverridden; + private int overriddenTooltipW, overriddenTooltipH; + /** Minimum tooltip dimensions when user-resized */ + private static final int MIN_TOOLTIP_W = 80; + private static final int MIN_TOOLTIP_H = 30; + /** Smooth scroll: target offset (pixels), current is animated toward it each frame */ private float targetScrollOffset; /** Vertical scroll offset for tooltip content (pixels) */ @@ -233,6 +243,8 @@ public class HoverState { isDraggingScrollbar = false; isDraggingTooltip = false; tooltipPositionOverridden = false; + isResizingTooltip = false; + tooltipSizeOverridden = false; tooltipScrollOffsetF = 0; targetScrollOffset = 0; tooltipMaxScroll = 0; @@ -451,6 +463,43 @@ public class HoverState { public int getOverriddenTooltipX() { return overriddenTooltipX; } public int getOverriddenTooltipY() { return overriddenTooltipY; } + // ==================== TOOLTIP PANEL RESIZE ==================== + + /** Returns true when the mouse is over the resize handle (bottom-right corner). */ + public boolean isMouseOverResizeHandle(int mx, int my) { + if (!tooltipVisible || tooltipPanelW <= 0) return false; + int rx = tooltipPanelX + tooltipPanelW - 8; + int ry = tooltipPanelY + tooltipPanelH - 8; + return mx >= rx && mx <= tooltipPanelX + tooltipPanelW + && my >= ry && my <= tooltipPanelY + tooltipPanelH; + } + + /** Begin resizing; captures current panel dimensions as the baseline. */ + public void startTooltipResize(int mouseX, int mouseY) { + isResizingTooltip = true; + resizeInitMouseX = mouseX; + resizeInitMouseY = mouseY; + resizeInitW = tooltipSizeOverridden ? overriddenTooltipW : tooltipPanelW; + resizeInitH = tooltipSizeOverridden ? overriddenTooltipH : tooltipPanelH; + } + + /** Called every frame while M1 is held to resize the tooltip panel. */ + public void updateTooltipResize(int mouseX, int mouseY) { + if (!isResizingTooltip) return; + overriddenTooltipW = Math.max(MIN_TOOLTIP_W, resizeInitW + (mouseX - resizeInitMouseX)); + overriddenTooltipH = Math.max(MIN_TOOLTIP_H, resizeInitH + (mouseY - resizeInitMouseY)); + tooltipSizeOverridden = true; + } + + public void releaseTooltipResize() { + isResizingTooltip = false; + } + + public boolean isResizingTooltip() { return isResizingTooltip; } + public boolean hasOverriddenSize() { return tooltipSizeOverridden; } + public int getOverriddenTooltipW() { return overriddenTooltipW; } + public int getOverriddenTooltipH() { return overriddenTooltipH; } + public boolean isDraggingScrollbar() { return isDraggingScrollbar; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java index 890d17e2b..88a1cef13 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java @@ -109,6 +109,21 @@ public class TokenHoverRenderer { // Box dimensions — use visibleContentHeight so positioning matches rendering int boxWidth = contentWidth + PADDING * 2; int boxHeight = visibleContentHeight + PADDING * 2; + + // Apply user size override when the panel has been resized + if (hoverState.hasOverriddenSize()) { + int overW = Math.max(MIN_WIDTH + PADDING * 2, hoverState.getOverriddenTooltipW()); + int overH = Math.max(lineHeight + PADDING * 2, hoverState.getOverriddenTooltipH()); + boxWidth = overW; + visibleContentHeight = overH - PADDING * 2; + boxHeight = overH; + // Re-measure with new wrap width so scrollbar detection is accurate + int newContentW = boxWidth - PADDING * 2; + totalContentHeight = calculateContentHeight(info, newContentW); + if (totalContentHeight > visibleContentHeight) { + totalContentHeight = calculateContentHeight(info, newContentW - 6); + } + } // Position the tooltip — use dragged position if user has panned the panel int tooltipX = hoverState.hasOverriddenPosition() ? hoverState.getOverriddenTooltipX() : tokenX; @@ -341,7 +356,7 @@ public class TokenHoverRenderer { if (hasScrollbar) { int scrollbarX = x + boxWidth - PADDING; int scrollbarTrackTop = y + PADDING; - int scrollbarTrackHeight = visibleContentHeight; + int scrollbarTrackHeight = visibleContentHeight - 4; // leave room for resize handle // Track Gui.drawRect(scrollbarX, scrollbarTrackTop, scrollbarX + 3, scrollbarTrackTop + scrollbarTrackHeight, 0x40888888); @@ -349,9 +364,12 @@ public class TokenHoverRenderer { float scrollRatio = (float) visibleContentHeight / totalContentHeight; int thumbHeight = Math.max(6, (int)(scrollbarTrackHeight * scrollRatio)); int effectiveMaxScroll = totalContentHeight - visibleContentHeight; + // Clamp scrollProgress to [0,1] — guards against stale offset after resize shrinks content float scrollProgress = effectiveMaxScroll > 0 - ? (float) hoverState.getTooltipScrollOffset() / effectiveMaxScroll : 0f; + ? Math.min(1f, (float) hoverState.getTooltipScrollOffset() / effectiveMaxScroll) : 0f; int thumbY = scrollbarTrackTop + (int)((scrollbarTrackHeight - thumbHeight) * scrollProgress); + // Clamp thumb within track bounds defensively + thumbY = Math.max(scrollbarTrackTop, Math.min(scrollbarTrackTop + scrollbarTrackHeight - thumbHeight, thumbY)); // Store thumb bounds in HoverState for drag/hover detection hoverState.setScrollbarThumb(scrollbarX, thumbY, thumbHeight, scrollbarTrackTop, scrollbarTrackHeight); // Highlight thumb when hovered or dragging (darker = active, like AutocompleteMenu) @@ -362,6 +380,24 @@ public class TokenHoverRenderer { int thumbColor = thumbActive ? 0xFFCCCCCC : 0xFF808080; Gui.drawRect(scrollbarX, thumbY, scrollbarX + 3, thumbY + thumbHeight, thumbColor); } + + // Draw resize handle at bottom-right corner (outside scissor, same as scrollbar) + { + int mouseX = hoverState.getLastMouseX(); + int mouseY = hoverState.getLastMouseY(); + boolean resizeActive = hoverState.isResizingTooltip() + || hoverState.isMouseOverResizeHandle(mouseX, mouseY); + int dotColor = resizeActive ? 0xFFCCCCCC : 0x80888888; + int rhX = x + boxWidth - 2; + int rhY = y + boxHeight - 2; + // Three-dot diagonal pattern (◢) + Gui.drawRect(rhX - 1, rhY - 1, rhX, rhY, dotColor); + Gui.drawRect(rhX - 3, rhY - 1, rhX - 2, rhY, dotColor); + Gui.drawRect(rhX - 1, rhY - 3, rhX, rhY - 2, dotColor); + Gui.drawRect(rhX - 5, rhY - 1, rhX - 4, rhY, dotColor); + Gui.drawRect(rhX - 3, rhY - 3, rhX - 2, rhY - 2, dotColor); + Gui.drawRect(rhX - 1, rhY - 5, rhX, rhY - 4, dotColor); + } } /**