This commit is contained in:
Kam committed 2026-02-05 17:27:13 -05:00
1 parent fb45a79e22
commit 3e28c3cace
8 files changed
+279 -110

No files matched your search

@@ -70,6 +70,15 @@ public class ScrollPanel extends Gui {
}
}
public void setBackgroundColor(int color) {
this.backgroundColor = color;
}
public void resetScroll() {
this.scrollY = 0;
this.targetScrollY = 0;
}
public int getContentHeight() {
return contentHeight;
}
@@ -7,9 +7,7 @@ import noppes.npcs.client.gui.util.IDialogEditorListener;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import noppes.npcs.controllers.data.Dialog;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
@@ -38,9 +36,9 @@ public class DialogEditorPanel extends Gui {
protected Dialog dialog;
protected boolean dirty = false;
protected int currentTab = 0;
protected float scrollY = 0;
protected float targetScrollY = 0;
protected int contentHeight = 0;
// === Scroll Panel (replaces manual scroll logic) ===
protected ScrollPanel scrollPanel;
// === Tab Names ===
protected static final String[] TAB_NAMES = {"Text", "Options", "Feedback", "Settings", "Availability"};
@@ -59,12 +57,6 @@ public class DialogEditorPanel extends Gui {
// === Listener ===
protected IDialogEditorListener listener;
// === Scrollbar ===
protected int scrollbarWidth = 6;
protected boolean isDraggingScrollbar = false;
protected int dragStartY;
protected float dragStartScroll;
// === Footer ===
protected boolean hoverTestBtn = false;
protected boolean hoverSaveBtn = false;
@@ -76,6 +68,18 @@ public class DialogEditorPanel extends Gui {
this.width = width;
this.height = height;
initTabs();
initScrollPanel();
}
protected void initScrollPanel() {
int contentY = y + headerHeight + tabBarHeight;
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
scrollPanel = new ScrollPanel(x, contentY, width, contentAreaH);
scrollPanel.setBackgroundColor(0); // Transparent - we draw our own background
}
protected int getScrollbarWidth() {
return scrollPanel != null ? 6 : 0;
}
// === Tab Initialization ===
@@ -97,8 +101,7 @@ public class DialogEditorPanel extends Gui {
public void setDialog(Dialog dialog) {
this.dialog = dialog;
this.dirty = false;
this.scrollY = 0;
this.targetScrollY = 0;
scrollPanel.resetScroll();
for (DialogEditorTab tab : tabs) {
tab.setDialog(dialog);
@@ -128,13 +131,6 @@ public class DialogEditorPanel extends Gui {
FontRenderer fr = Minecraft.getMinecraft().fontRenderer;
// Update scroll animation
if (Math.abs(scrollY - targetScrollY) > 0.5f) {
scrollY += (targetScrollY - scrollY) * 0.3f;
} else {
scrollY = targetScrollY;
}
// Background
drawRect(x, y, x + width, y + height, ModernColors.PANEL_BG_SOLID);
@@ -144,44 +140,24 @@ public class DialogEditorPanel extends Gui {
// Tab bar
drawTabBar(mouseX, mouseY, fr);
// Content area with scissor
int contentY = y + headerHeight + tabBarHeight;
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
Minecraft mc = Minecraft.getMinecraft();
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int scale = sr.getScaleFactor();
// Update screen positions for all dropdowns BEFORE scroll translation
updateDropdownScreenPositions();
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(x * scale, mc.displayHeight - (contentY + contentAreaH) * scale,
(width - scrollbarWidth) * scale, contentAreaH * scale);
// Draw scrollable content using ScrollPanel
int contentX = x + padding;
int contentW = width - padding * 2 - getScrollbarWidth();
int startY = y + headerHeight + tabBarHeight + padding;
GL11.glPushMatrix();
GL11.glTranslatef(0, -scrollY, 0);
scrollPanel.beginDraw();
// Draw current tab content
int contentX = x + padding;
int contentW = width - padding * 2 - scrollbarWidth;
int startY = y + headerHeight + tabBarHeight + padding;
contentHeight = tabs[currentTab].draw(contentX, contentW, startY, mouseX, (int)(mouseY + scrollY), fr);
int contentHeight = tabs[currentTab].draw(contentX, contentW, startY,
mouseX, scrollPanel.toContentY(mouseY), fr);
GL11.glPopMatrix();
GL11.glDisable(GL11.GL_SCISSOR_TEST);
// Update scroll panel's content height
scrollPanel.setContentHeight(contentHeight);
// Clamp scroll position when content height changes
int maxScroll = Math.max(0, contentHeight - contentAreaH);
if (scrollY > maxScroll) {
scrollY = maxScroll;
targetScrollY = maxScroll;
}
// Draw scrollbar
if (contentHeight > contentAreaH) {
drawScrollbar(mouseX, mouseY, contentY, contentAreaH);
}
scrollPanel.endDraw(mouseX, mouseY);
// Draw expanded dropdowns in SCREEN SPACE (no translation!)
drawExpandedDropdownsScreenSpace(mouseX, mouseY);
@@ -242,21 +218,6 @@ public class DialogEditorPanel extends Gui {
}
}
protected void drawScrollbar(int mouseX, int mouseY, int contentY, int contentAreaH) {
int sbX = x + width - scrollbarWidth;
drawRect(sbX, contentY, sbX + scrollbarWidth, contentY + contentAreaH, ModernColors.SCROLLBAR_BG);
float viewRatio = (float) contentAreaH / contentHeight;
int thumbH = Math.max(20, (int)(contentAreaH * viewRatio));
int maxScroll = contentHeight - contentAreaH;
int thumbY = maxScroll > 0 ? (int)((contentAreaH - thumbH) * (scrollY / maxScroll)) : 0;
boolean hoverThumb = mouseX >= sbX && mouseX < sbX + scrollbarWidth &&
mouseY >= contentY + thumbY && mouseY < contentY + thumbY + thumbH;
int thumbColor = (hoverThumb || isDraggingScrollbar) ? ModernColors.SCROLLBAR_THUMB_HOVER : ModernColors.SCROLLBAR_THUMB;
drawRect(sbX + 1, contentY + thumbY, sbX + scrollbarWidth - 1, contentY + thumbY + thumbH, thumbColor);
}
protected void drawFooter(int mouseX, int mouseY, FontRenderer fr) {
int footerY = y + height - footerHeight;
drawRect(x, footerY, x + width, y + height, ModernColors.SECTION_HEADER_BG);
@@ -301,7 +262,7 @@ public class DialogEditorPanel extends Gui {
protected void updateDropdownScreenPositions() {
for (ModernDropdown dropdown : getAllDropdowns()) {
int screenDropdownY = dropdown.getY() - (int)scrollY;
int screenDropdownY = scrollPanel.toScreenY(dropdown.getY());
dropdown.setScreenPosition(dropdown.getX(), screenDropdownY);
}
}
@@ -336,8 +297,7 @@ public class DialogEditorPanel extends Gui {
// Tab clicks
if (hoveredTab >= 0 && mouseY >= y + headerHeight && mouseY < y + headerHeight + tabBarHeight) {
currentTab = hoveredTab;
scrollY = 0;
targetScrollY = 0;
scrollPanel.resetScroll();
closeAllDropdowns();
return true;
}
@@ -356,14 +316,8 @@ public class DialogEditorPanel extends Gui {
return true;
}
// Scrollbar
int contentY = y + headerHeight + tabBarHeight;
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
int sbX = x + width - scrollbarWidth;
if (contentHeight > contentAreaH && mouseX >= sbX && mouseX < sbX + scrollbarWidth) {
isDraggingScrollbar = true;
dragStartY = mouseY;
dragStartScroll = scrollY;
// Scrollbar - delegate to ScrollPanel
if (scrollPanel.mouseClicked(mouseX, mouseY, button)) {
return true;
}
@@ -381,7 +335,7 @@ public class DialogEditorPanel extends Gui {
}
// Adjust mouseY for scroll (for components inside scrollable content)
int adjMouseY = (int)(mouseY + scrollY);
int adjMouseY = scrollPanel.toContentY(mouseY);
// Delegate to current tab
return tabs[currentTab].mouseClicked(mouseX, adjMouseY, button);
@@ -393,37 +347,18 @@ public class DialogEditorPanel extends Gui {
}
public void mouseScrolled(int delta) {
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
if (contentHeight <= contentAreaH) return;
int maxScroll = contentHeight - contentAreaH;
targetScrollY = Math.max(0, Math.min(targetScrollY - delta * 20, maxScroll));
scrollPanel.mouseScrolled(delta);
}
public void mouseReleased(int mouseX, int mouseY) {
isDraggingScrollbar = false;
scrollPanel.mouseReleased(mouseX, mouseY);
// Let text areas handle mouse release
textTab.getTextArea().mouseReleased(mouseX, mouseY);
settingsTab.getCommandArea().mouseReleased(mouseX, mouseY);
}
public void mouseDragged(int mouseX, int mouseY) {
if (isDraggingScrollbar) {
int contentY = y + headerHeight + tabBarHeight;
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
int maxScroll = contentHeight - contentAreaH;
float viewRatio = (float) contentAreaH / contentHeight;
int thumbH = Math.max(20, (int)(contentAreaH * viewRatio));
int trackH = contentAreaH - thumbH;
if (trackH > 0) {
int deltaY = mouseY - dragStartY;
float scrollDelta = (deltaY / (float)trackH) * maxScroll;
scrollY = Math.max(0, Math.min(dragStartScroll + scrollDelta, maxScroll));
targetScrollY = scrollY;
}
}
scrollPanel.mouseDragged(mouseX, mouseY);
textTab.getTextArea().mouseDragged(mouseX, mouseY);
settingsTab.getCommandArea().mouseDragged(mouseX, mouseY);
}
@@ -457,6 +392,10 @@ public class DialogEditorPanel extends Gui {
this.y = y;
this.width = width;
this.height = height;
// Update scroll panel bounds
int contentY = y + headerHeight + tabBarHeight;
int contentAreaH = height - headerHeight - tabBarHeight - footerHeight;
scrollPanel.setBounds(x, contentY, width, contentAreaH);
}
public void setListener(IDialogEditorListener listener) {
@@ -195,14 +195,27 @@ public class ModernColorPicker extends ModernSubGuiInterface {
/**
* Check if mouse is inside the color wheel area.
* Uses circular bounds for accurate detection.
*/
private boolean isInsideWheel(int mouseX, int mouseY) {
return mouseX >= wheelX && mouseX < wheelX + wheelSize &&
mouseY >= wheelY && mouseY < wheelY + wheelSize;
// First check rectangle bounds
if (mouseX < wheelX || mouseX >= wheelX + wheelSize ||
mouseY < wheelY || mouseY >= wheelY + wheelSize) {
return false;
}
// Then check circular bounds (wheel is circular)
int centerX = wheelX + wheelSize / 2;
int centerY = wheelY + wheelSize / 2;
int radius = wheelSize / 2;
int dx = mouseX - centerX;
int dy = mouseY - centerY;
return (dx * dx + dy * dy) <= (radius * radius);
}
/**
* Pick color from the wheel texture at clicked position.
* Uses the same 4x multiplier as the original SubGuiColorSelector
* since the texture is 480x480 and we display at variable size.
*/
private void pickColorFromWheel(int mouseX, int mouseY) {
InputStream stream = null;
@@ -210,8 +223,9 @@ public class ModernColorPicker extends ModernSubGuiInterface {
IResource resource = mc.getResourceManager().getResource(COLOR_WHEEL);
BufferedImage image = ImageIO.read(stream = resource.getInputStream());
// Scale mouse position to texture coordinates
// The texture is assumed to be a certain size, compute ratio
// Calculate texture coordinates using the same ratio as original
// Original: draws 120x120, texture is 480x480, uses *4 multiplier
// We draw wheelSize x wheelSize, so scale proportionally
int texX = (mouseX - wheelX) * image.getWidth() / wheelSize;
int texY = (mouseY - wheelY) * image.getHeight() / wheelSize;
@@ -219,8 +233,13 @@ public class ModernColorPicker extends ModernSubGuiInterface {
texX = Math.max(0, Math.min(texX, image.getWidth() - 1));
texY = Math.max(0, Math.min(texY, image.getHeight() - 1));
color = image.getRGB(texX, texY) & 0xFFFFFF;
hexField.setText(getHexString());
// Get pixel color, ignoring alpha and transparent pixels
int pixel = image.getRGB(texX, texY);
int alpha = (pixel >> 24) & 0xFF;
if (alpha > 0) {
color = pixel & 0xFFFFFF;
hexField.setText(getHexString());
}
} catch (IOException e) {
// Ignore errors
@@ -21,7 +21,7 @@ import java.util.*;
* Features two-column layout (domains | sounds), search fields, and preview button.
*/
public class ModernSoundSelector extends ModernSubGuiInterface {
`
// Selected values
private String selectedDomain;
public ResourceLocation selectedResource;
@@ -17,6 +17,8 @@ public class ModernSubGuiInterface extends SubGuiInterface {
// Overlay color (semi-transparent black)
protected int overlayColor = 0x80000000;
// Whether to draw overlay over entire screen (true) or just around panel (false)
protected boolean fullScreenOverlay = false;
// Panel colors
protected int panelBg = ModernColors.PANEL_BG_SOLID;
@@ -36,8 +38,20 @@ public class ModernSubGuiInterface extends SubGuiInterface {
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
// Draw semi-transparent overlay over entire screen
Gui.drawRect(0, 0, width, height, overlayColor);
// Draw semi-transparent overlay
// Only darken a region around the panel, not the entire screen
// This allows the diagram/tree view to remain visible
if (fullScreenOverlay) {
Gui.drawRect(0, 0, width, height, overlayColor);
} else {
// Draw overlay only behind and slightly around the panel
int margin = 20;
int overlayLeft = Math.max(0, guiLeft - margin);
int overlayTop = Math.max(0, guiTop - margin);
int overlayRight = Math.min(width, guiLeft + xSize + margin);
int overlayBottom = Math.min(height, guiTop + ySize + margin);
Gui.drawRect(overlayLeft, overlayTop, overlayRight, overlayBottom, overlayColor);
}
// Draw modern panel background
drawModernPanel();
@@ -144,6 +158,14 @@ public class ModernSubGuiInterface extends SubGuiInterface {
this.overlayColor = color;
}
/**
* Set whether to draw overlay over entire screen or just around panel.
* Default is false (localized overlay to preserve visibility of parent content).
*/
public void setFullScreenOverlay(boolean fullScreen) {
this.fullScreenOverlay = fullScreen;
}
/**
* Get content area Y start (after header if present).
*/
@@ -399,7 +399,9 @@ public abstract class GuiAdvancedDiagram extends Gui {
// ===== Drawing =====
public void drawDiagram(int mouseX, int mouseY) {
if (parent != null && parent.hasSubGui()) return;
// Note: We removed the hasSubGui() check here. The diagram should continue
// drawing even when a SubGui is open - the SubGui renders on top via z-order.
// Previously this caused the entire diagram to disappear when opening dialogs.
if (isWithin(mouseX, mouseY)) {
handleMouseScroll(Mouse.getDWheel());
@@ -94,6 +94,74 @@ public class GuiNpcTextArea extends GuiNpcTextField {
public boolean textboxKeyTyped(char c, int i) {
if (isFocused() && canEdit) {
String originalText = getText();
// Handle Ctrl+A (Select All) - char code 1
if (GuiScreen.isCtrlKeyDown() && (i == Keyboard.KEY_A || c == 1)) {
cursorPosition = originalText.length();
setSelectionPos(0);
return true;
}
// Handle Ctrl+C (Copy) - char code 3
if (GuiScreen.isCtrlKeyDown() && (i == Keyboard.KEY_C || c == 3)) {
String selected = getSelectedText();
if (selected != null && !selected.isEmpty()) {
GuiScreen.setClipboardString(selected);
}
return true;
}
// Handle Ctrl+V (Paste) - char code 22
if (GuiScreen.isCtrlKeyDown() && (i == Keyboard.KEY_V || c == 22)) {
String clipboard = GuiScreen.getClipboardString();
if (clipboard != null && !clipboard.isEmpty()) {
// Delete selected text first if any
String selected = getSelectedText();
int selStart = getSelectionEnd();
int selEnd = cursorPosition;
if (selStart > selEnd) {
int tmp = selStart;
selStart = selEnd;
selEnd = tmp;
}
if (selected != null && !selected.isEmpty()) {
String before = originalText.substring(0, selStart);
String after = originalText.substring(selEnd);
this.setText(before + clipboard + after);
cursorPosition = selStart + clipboard.length();
} else {
String before = originalText.substring(0, cursorPosition);
String after = originalText.substring(cursorPosition);
this.setText(before + clipboard + after);
cursorPosition += clipboard.length();
}
updateLineList();
}
return true;
}
// Handle Ctrl+X (Cut) - char code 24
if (GuiScreen.isCtrlKeyDown() && (i == Keyboard.KEY_X || c == 24)) {
String selected = getSelectedText();
if (selected != null && !selected.isEmpty()) {
GuiScreen.setClipboardString(selected);
// Delete selected text
int selStart = getSelectionEnd();
int selEnd = cursorPosition;
if (selStart > selEnd) {
int tmp = selStart;
selStart = selEnd;
selEnd = tmp;
}
String before = originalText.substring(0, selStart);
String after = originalText.substring(selEnd);
this.setText(before + after);
cursorPosition = selStart;
updateLineList();
}
return true;
}
// Handle newline specially - insert and return immediately
if (c == '\r' || c == '\n') {
this.setText(originalText.substring(0, cursorPosition) + c + originalText.substring(cursorPosition));
@@ -1,10 +1,21 @@
package noppes.npcs.client.gui.util;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
public class SubGuiInterface extends GuiNPCInterface {
public GuiScreen parent;
// === Modern Style Option ===
protected boolean useModernStyle = false;
protected int modernOverlayColor = 0x80000000;
protected int modernPanelBg = 0xFF1E1E2E;
protected int modernPanelBorder = 0xFF4488CC;
protected int modernHeaderBg = 0xFF2A2A3A;
protected int modernHeaderText = 0xFFFFFF;
protected String modernHeaderTitle = null;
protected int modernHeaderHeight = 20;
@Override
public void save() {
@@ -34,6 +45,105 @@ public class SubGuiInterface extends GuiNPCInterface {
@Override
public void drawScreen(int i, int j, float f) {
super.drawScreen(i, j, f);
if (useModernStyle) {
drawModernStyle(i, j, f);
} else {
super.drawScreen(i, j, f);
}
}
/**
* Draw with modern styling - dark overlay, modern panel, accent border.
*/
protected void drawModernStyle(int mouseX, int mouseY, float partialTicks) {
// Draw semi-transparent overlay behind panel
int margin = 20;
int overlayLeft = Math.max(0, guiLeft - margin);
int overlayTop = Math.max(0, guiTop - margin);
int overlayRight = Math.min(width, guiLeft + xSize + margin);
int overlayBottom = Math.min(height, guiTop + ySize + margin);
Gui.drawRect(overlayLeft, overlayTop, overlayRight, overlayBottom, modernOverlayColor);
// Draw panel border
Gui.drawRect(guiLeft - 1, guiTop - 1, guiLeft + xSize + 1, guiTop + ySize + 1, modernPanelBorder);
// Draw panel background
Gui.drawRect(guiLeft, guiTop, guiLeft + xSize, guiTop + ySize, modernPanelBg);
// Draw header if set
if (modernHeaderTitle != null) {
Gui.drawRect(guiLeft, guiTop, guiLeft + xSize, guiTop + modernHeaderHeight, modernHeaderBg);
int textW = fontRendererObj.getStringWidth(modernHeaderTitle);
int textX = guiLeft + (xSize - textW) / 2;
int textY = guiTop + (modernHeaderHeight - fontRendererObj.FONT_HEIGHT) / 2;
fontRendererObj.drawString(modernHeaderTitle, textX, textY, modernHeaderText);
}
// Draw buttons, labels, textfields, scrolls from parent
for (Object obj : buttonList) {
if (obj instanceof net.minecraft.client.gui.GuiButton) {
((net.minecraft.client.gui.GuiButton) obj).drawButton(mc, mouseX, mouseY);
}
}
for (GuiNpcLabel label : labels.values()) {
label.drawLabel(this, fontRendererObj);
}
for (GuiNpcTextField tf : textfields.values()) {
tf.drawTextBox(mouseX, mouseY);
}
for (GuiCustomScroll scroll : scrolls.values()) {
scroll.drawScreen(mouseX, mouseY, 0, scroll.isMouseOver(mouseX, mouseY) ? org.lwjgl.input.Mouse.getDWheel() : 0);
}
// Allow subclasses to draw custom content
drawModernContent(mouseX, mouseY, partialTicks);
}
/**
* Override to draw custom content in modern style.
*/
protected void drawModernContent(int mouseX, int mouseY, float partialTicks) {
// Override in subclasses
}
// === Modern Style Configuration ===
/**
* Enable modern styling for this SubGui.
*/
public void setUseModernStyle(boolean modern) {
this.useModernStyle = modern;
if (modern) {
drawDefaultBackground = false;
}
}
/**
* Set the header title for modern style. Pass null to disable header.
*/
public void setModernHeaderTitle(String title) {
this.modernHeaderTitle = title;
}
/**
* Set panel colors for modern style.
*/
public void setModernPanelColors(int background, int border) {
this.modernPanelBg = background;
this.modernPanelBorder = border;
}
/**
* Get content area Y start (after header if present).
*/
protected int getModernContentY() {
return guiTop + (modernHeaderTitle != null ? modernHeaderHeight : 0);
}
/**
* Get content area height (excluding header if present).
*/
protected int getModernContentHeight() {
return ySize - (modernHeaderTitle != null ? modernHeaderHeight : 0);
}
}