mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 06:07:27 +00:00
Search/Replace BAR FOR SCRIPT EDITOR FUUUUUUUUH
This commit is contained in:
1 parent
c7c516d97e
commit
0fce002b52
3 files changed
+1009
No files matched your search
@@ -75,6 +75,8 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
public static final ScriptEditorKeys KEYS = new ScriptEditorKeys();
|
||||
public OverlayKeyPresetViewer KEYS_OVERLAY = new OverlayKeyPresetViewer(KEYS);
|
||||
|
||||
// ==================== SEARCH/REPLACE ====================
|
||||
private final SearchReplaceBar searchBar = new SearchReplaceBar();
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
@@ -100,6 +102,46 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
KEYS_OVERLAY.viewButton.scale = 0.45f;
|
||||
KEYS_OVERLAY.viewButton.initGui(endX + xOffset, endY - 26);
|
||||
|
||||
// Initialize search bar
|
||||
searchBar.initGui(x, y, width);
|
||||
searchBar.setCallback(new SearchReplaceBar.SearchCallback() {
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String newText) {
|
||||
GuiScriptTextArea.this.setText(newText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToPosition(int position) {
|
||||
// Find line containing position and scroll to it
|
||||
if (container == null || container.lines == null) return;
|
||||
for (int i = 0; i < container.lines.size(); i++) {
|
||||
LineData ld = container.lines.get(i);
|
||||
if (position >= ld.start && position < ld.end) {
|
||||
int visible = Math.max(1, container.visibleLines);
|
||||
int maxScroll = Math.max(0, container.linesCount - visible);
|
||||
scroll.scrollToLine(i, visible, maxScroll);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int start, int end) {
|
||||
selection.setSelection(start, end);
|
||||
selection.setCursorPositionDirect(end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGutterWidth() {
|
||||
return LINE_NUMBER_GUTTER_WIDTH;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== RENDERING ====================
|
||||
@@ -289,6 +331,29 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight search matches
|
||||
List<int[]> searchMatches = searchBar.getMatches();
|
||||
int currentMatchIdx = searchBar.getCurrentMatchIndex();
|
||||
for (int mi = 0; mi < searchMatches.size(); mi++) {
|
||||
int[] match = searchMatches.get(mi);
|
||||
// Check if match overlaps with this line
|
||||
if (match[1] > data.start && match[0] < data.end) {
|
||||
int matchStart = Math.max(match[0] - data.start, 0);
|
||||
int matchEnd = Math.min(match[1] - data.start, line.length());
|
||||
if (matchStart < matchEnd) {
|
||||
int s = ClientProxy.Font.width(line.substring(0, matchStart));
|
||||
int e = ClientProxy.Font.width(line.substring(0, matchEnd)) + 1;
|
||||
// Current match gets brighter highlight, others get dimmer
|
||||
int highlightColor = (mi == currentMatchIdx) ? 0xBB4488ff : 0x662266aa;
|
||||
if (searchBar.isMatchExcluded(mi)) {
|
||||
highlightColor = 0x33666666; // Dimmer for excluded matches
|
||||
}
|
||||
drawRect(x + LINE_NUMBER_GUTTER_WIDTH + 1 + s, posY, x + LINE_NUMBER_GUTTER_WIDTH + 1 + e, posY + container.lineHeight, highlightColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the current line (light gray) under any selection
|
||||
if (active && isEnabled() && (selection.getCursorPosition() >= data.start && selection.getCursorPosition() < data.end || (i == list.size() - 1 && selection.getCursorPosition() == text.length()))) {
|
||||
drawRect(x , posY, x + width - 1, posY + container.lineHeight, 0x22e0e0e0);
|
||||
@@ -367,6 +432,9 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
drawRect(posX, posY, posX + 5, posY + sbSize + 2, 0xFFe0e0e0);
|
||||
}
|
||||
|
||||
// Draw search/replace bar (overlays viewport)
|
||||
searchBar.draw(xMouse, yMouse);
|
||||
|
||||
KEYS_OVERLAY.draw(xMouse, yMouse, wheelDelta);
|
||||
}
|
||||
|
||||
@@ -622,6 +690,20 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// SEARCH: Open search bar (Ctrl+R)
|
||||
KEYS.SEARCH.setTask(e -> {
|
||||
if (!e.isPress() || !active || !isEnabled())
|
||||
return;
|
||||
searchBar.toggle();
|
||||
});
|
||||
|
||||
// SEARCH_REPLACE: Open search+replace bar (Ctrl+Shift+R)
|
||||
KEYS.SEARCH_REPLACE.setTask(e -> {
|
||||
if (!e.isPress() || !active || !isEnabled())
|
||||
return;
|
||||
searchBar.toggleReplace();
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== KEYBOARD INPUT HANDLING ====================
|
||||
@@ -634,6 +716,13 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
public boolean textboxKeyTyped(char c, int i) {
|
||||
KEYS_OVERLAY.keyTyped(c, i);
|
||||
|
||||
// Handle search bar input first if it has focus
|
||||
if (searchBar.hasFocus()) {
|
||||
if (searchBar.keyTyped(c, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!active || KEYS_OVERLAY.showOverlay) return false;
|
||||
|
||||
if (this.isKeyComboCtrlA(i)) {
|
||||
@@ -1415,6 +1504,11 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
// ==================== MOUSE HANDLING ====================
|
||||
|
||||
public void mouseClicked(int xMouse, int yMouse, int mouseButton) {
|
||||
// Check search bar clicks first
|
||||
if (searchBar.isVisible() && searchBar.mouseClicked(xMouse, yMouse, mouseButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYS_OVERLAY.showOverlay) {
|
||||
KEYS_OVERLAY.mouseClicked(xMouse, yMouse, mouseButton);
|
||||
return;
|
||||
@@ -1476,6 +1570,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
// Called from GuiScreen.updateScreen()
|
||||
public void updateCursorCounter() {
|
||||
KEYS.tick();
|
||||
searchBar.updateCursor();
|
||||
++this.cursorCounter;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,909 @@
|
||||
package noppes.npcs.client.gui.util.script;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.Gui;
|
||||
import net.minecraft.util.ChatAllowedCharacters;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import noppes.npcs.client.ClientProxy;
|
||||
import noppes.npcs.client.gui.util.GuiUtil;
|
||||
import noppes.npcs.client.gui.util.key.OverlayKeyPresetViewer;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Search and Replace bar component for the script editor.
|
||||
* Provides IntelliJ-like search functionality with:
|
||||
* - Search text field with rounded edges
|
||||
* - Match case (Cc) toggle
|
||||
* - Whole word (W) toggle
|
||||
* - Previous/Next match navigation
|
||||
* - Replace functionality with Replace/Replace All/Exclude buttons
|
||||
* - Auto-scroll to matches
|
||||
* - All matches highlighting
|
||||
*/
|
||||
public class SearchReplaceBar {
|
||||
|
||||
// ==================== TEXTURE ====================
|
||||
public static final ResourceLocation TEXTURE = OverlayKeyPresetViewer.TEXTURE;
|
||||
|
||||
// ==================== DIMENSIONS ====================
|
||||
private int x, y, width;
|
||||
private int barHeight = 24;
|
||||
private int replaceBarHeight = 22;
|
||||
private int textFieldWidth = 150;
|
||||
private int textFieldHeight = 16;
|
||||
private int buttonSize = 16;
|
||||
private int buttonSpacing = 2;
|
||||
private int padding = 4;
|
||||
|
||||
// ==================== STATE ====================
|
||||
private boolean visible = false;
|
||||
private boolean showReplace = false;
|
||||
private boolean searchFieldFocused = true;
|
||||
private boolean replaceFieldFocused = false;
|
||||
|
||||
// ==================== SEARCH OPTIONS ====================
|
||||
private boolean matchCase = false;
|
||||
private boolean wholeWord = false;
|
||||
|
||||
// ==================== TEXT FIELDS ====================
|
||||
private String searchText = "";
|
||||
private String replaceText = "";
|
||||
private int searchCursor = 0;
|
||||
private int replaceCursor = 0;
|
||||
private int searchSelectionStart = 0;
|
||||
private int searchSelectionEnd = 0;
|
||||
private int replaceSelectionStart = 0;
|
||||
private int replaceSelectionEnd = 0;
|
||||
|
||||
// ==================== MATCHES ====================
|
||||
private List<int[]> matches = new ArrayList<>();
|
||||
private int currentMatchIndex = -1;
|
||||
private List<Integer> excludedMatches = new ArrayList<>();
|
||||
|
||||
// ==================== HOVER STATE ====================
|
||||
private boolean hoverClose = false;
|
||||
private boolean hoverMatchCase = false;
|
||||
private boolean hoverWholeWord = false;
|
||||
private boolean hoverPrev = false;
|
||||
private boolean hoverNext = false;
|
||||
private boolean hoverReplace = false;
|
||||
private boolean hoverReplaceAll = false;
|
||||
private boolean hoverExclude = false;
|
||||
|
||||
// ==================== CURSOR BLINK ====================
|
||||
private int cursorCounter = 0;
|
||||
|
||||
// ==================== CALLBACK ====================
|
||||
private SearchCallback callback;
|
||||
|
||||
private final FontRenderer font = Minecraft.getMinecraft().fontRenderer;
|
||||
|
||||
/**
|
||||
* Callback interface for search/replace operations
|
||||
*/
|
||||
public interface SearchCallback {
|
||||
String getText();
|
||||
void setText(String text);
|
||||
void scrollToPosition(int position);
|
||||
void setSelection(int start, int end);
|
||||
int getGutterWidth();
|
||||
}
|
||||
|
||||
public SearchReplaceBar() {
|
||||
}
|
||||
|
||||
public void setCallback(SearchCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize bar position based on text area bounds
|
||||
*/
|
||||
public void initGui(int textAreaX, int textAreaY, int textAreaWidth) {
|
||||
this.x = textAreaX;
|
||||
this.y = textAreaY;
|
||||
this.width = textAreaWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open search bar (Ctrl+R)
|
||||
*/
|
||||
public void openSearch() {
|
||||
visible = true;
|
||||
showReplace = false;
|
||||
searchFieldFocused = true;
|
||||
replaceFieldFocused = false;
|
||||
searchSelectionStart = 0;
|
||||
searchSelectionEnd = searchText.length();
|
||||
searchCursor = searchText.length();
|
||||
updateMatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open search+replace bar (Ctrl+Shift+R)
|
||||
*/
|
||||
public void openSearchReplace() {
|
||||
visible = true;
|
||||
showReplace = true;
|
||||
searchFieldFocused = true;
|
||||
replaceFieldFocused = false;
|
||||
searchSelectionStart = 0;
|
||||
searchSelectionEnd = searchText.length();
|
||||
searchCursor = searchText.length();
|
||||
updateMatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the search bar
|
||||
*/
|
||||
public void close() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibility
|
||||
*/
|
||||
public void toggle() {
|
||||
if (visible && !showReplace) {
|
||||
close();
|
||||
} else {
|
||||
openSearch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle replace mode
|
||||
*/
|
||||
public void toggleReplace() {
|
||||
if (visible && showReplace) {
|
||||
close();
|
||||
} else {
|
||||
openSearchReplace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public boolean isSearchFocused() {
|
||||
return visible && searchFieldFocused;
|
||||
}
|
||||
|
||||
public boolean isReplaceFocused() {
|
||||
return visible && replaceFieldFocused;
|
||||
}
|
||||
|
||||
public boolean hasFocus() {
|
||||
return visible && (searchFieldFocused || replaceFieldFocused);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total height of the bar (for viewport offset)
|
||||
*/
|
||||
public int getTotalHeight() {
|
||||
if (!visible) return 0;
|
||||
return showReplace ? barHeight + replaceBarHeight : barHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all current matches for highlighting
|
||||
*/
|
||||
public List<int[]> getMatches() {
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current match index
|
||||
*/
|
||||
public int getCurrentMatchIndex() {
|
||||
return currentMatchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a match is excluded
|
||||
*/
|
||||
public boolean isMatchExcluded(int index) {
|
||||
return excludedMatches.contains(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cursor blink counter
|
||||
*/
|
||||
public void updateCursor() {
|
||||
cursorCounter++;
|
||||
}
|
||||
|
||||
// ==================== DRAWING ====================
|
||||
|
||||
/**
|
||||
* Draw the search/replace bar
|
||||
*/
|
||||
public void draw(int mouseX, int mouseY) {
|
||||
if (!visible) return;
|
||||
|
||||
updateHoverStates(mouseX, mouseY);
|
||||
|
||||
int totalHeight = getTotalHeight();
|
||||
Gui.drawRect(x, y, x + width, y + totalHeight, 0xFF3c3f41);
|
||||
Gui.drawRect(x, y + totalHeight - 1, x + width, y + totalHeight, 0xFF515151);
|
||||
|
||||
drawSearchRow(mouseX, mouseY);
|
||||
|
||||
if (showReplace) {
|
||||
drawReplaceRow(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSearchRow(int mouseX, int mouseY) {
|
||||
int rowY = y + padding;
|
||||
int currentX = x + padding;
|
||||
|
||||
drawTextField(currentX, rowY, textFieldWidth, textFieldHeight,
|
||||
searchText, searchCursor, searchSelectionStart, searchSelectionEnd,
|
||||
searchFieldFocused, "Search...");
|
||||
currentX += textFieldWidth + buttonSpacing + 4;
|
||||
|
||||
String matchInfo = matches.isEmpty() ? "No matches" :
|
||||
(currentMatchIndex + 1) + "/" + matches.size();
|
||||
int matchInfoWidth = font.getStringWidth(matchInfo);
|
||||
font.drawString(matchInfo, currentX, rowY + 4, matches.isEmpty() ? 0xFF888888 : 0xFFcccccc);
|
||||
currentX += matchInfoWidth + 8;
|
||||
|
||||
drawToggleButton(currentX, rowY, "Cc", matchCase, hoverMatchCase, "Match Case");
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
drawToggleButton(currentX, rowY, "W", wholeWord, hoverWholeWord, "Whole Word");
|
||||
currentX += buttonSize + buttonSpacing + 4;
|
||||
|
||||
boolean prevEnabled = !matches.isEmpty();
|
||||
drawNavButton(currentX, rowY, "▲", prevEnabled, hoverPrev);
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
boolean nextEnabled = !matches.isEmpty();
|
||||
drawNavButton(currentX, rowY, "▼", nextEnabled, hoverNext);
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
int closeX = x + width - buttonSize - padding;
|
||||
drawCloseButton(closeX, rowY, hoverClose);
|
||||
}
|
||||
|
||||
private void drawReplaceRow(int mouseX, int mouseY) {
|
||||
int rowY = y + barHeight + 2;
|
||||
int currentX = x + padding;
|
||||
|
||||
drawTextField(currentX, rowY, textFieldWidth, textFieldHeight,
|
||||
replaceText, replaceCursor, replaceSelectionStart, replaceSelectionEnd,
|
||||
replaceFieldFocused, "Replace...");
|
||||
currentX += textFieldWidth + buttonSpacing + 4;
|
||||
|
||||
drawActionButton(currentX, rowY, "Replace", hoverReplace, !matches.isEmpty());
|
||||
currentX += font.getStringWidth("Replace") + 12 + buttonSpacing;
|
||||
|
||||
drawActionButton(currentX, rowY, "Replace All", hoverReplaceAll, !matches.isEmpty());
|
||||
currentX += font.getStringWidth("Replace All") + 12 + buttonSpacing;
|
||||
|
||||
drawActionButton(currentX, rowY, "Exclude", hoverExclude, currentMatchIndex >= 0);
|
||||
}
|
||||
|
||||
private void drawTextField(int fieldX, int fieldY, int fieldWidth, int fieldHeight,
|
||||
String text, int cursor, int selStart, int selEnd,
|
||||
boolean focused, String placeholder) {
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
|
||||
float boxScaleX = fieldWidth / 32f;
|
||||
GL11.glScalef(boxScaleX, 1, 1);
|
||||
|
||||
if (focused) {
|
||||
GL11.glColor4f(0.28f, 0.45f, 0.7f, 1f);
|
||||
} else {
|
||||
GL11.glColor4f(0.33f, 0.33f, 0.33f, 1f);
|
||||
}
|
||||
GuiUtil.drawTexturedModalRect(fieldX / boxScaleX, fieldY - 2, 32, 20, 0, 492);
|
||||
GL11.glPopMatrix();
|
||||
|
||||
int textX = fieldX + 4;
|
||||
int textY = fieldY + (fieldHeight - font.FONT_HEIGHT) / 2;
|
||||
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GuiUtil.setScissorClip(fieldX + 2, fieldY, fieldWidth - 4, fieldHeight);
|
||||
|
||||
if (text.isEmpty() && !focused) {
|
||||
font.drawString(placeholder, textX, textY, 0xFF808080);
|
||||
} else {
|
||||
if (selStart != selEnd && focused) {
|
||||
int startX = textX + font.getStringWidth(text.substring(0, Math.min(selStart, text.length())));
|
||||
int endX = textX + font.getStringWidth(text.substring(0, Math.min(selEnd, text.length())));
|
||||
Gui.drawRect(startX, textY - 1, endX, textY + font.FONT_HEIGHT, 0xFF2d5ca6);
|
||||
}
|
||||
|
||||
font.drawString(text, textX, textY, 0xFFe0e0e0);
|
||||
|
||||
if (focused && (cursorCounter / 10) % 2 == 0) {
|
||||
int cursorX = textX + font.getStringWidth(text.substring(0, Math.min(cursor, text.length())));
|
||||
Gui.drawRect(cursorX, textY - 1, cursorX + 1, textY + font.FONT_HEIGHT, 0xFFffffff);
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
private void drawToggleButton(int btnX, int btnY, String label, boolean active, boolean hovered, String tooltip) {
|
||||
int bgColor = active ? 0xFF4a6da7 : (hovered ? 0xFF505050 : 0xFF3c3c3c);
|
||||
Gui.drawRect(btnX, btnY, btnX + buttonSize, btnY + buttonSize, bgColor);
|
||||
Gui.drawRect(btnX, btnY, btnX + buttonSize, btnY + 1, 0xFF606060);
|
||||
Gui.drawRect(btnX, btnY + buttonSize - 1, btnX + buttonSize, btnY + buttonSize, 0xFF2a2a2a);
|
||||
|
||||
int textColor = active ? 0xFFffffff : (hovered ? 0xFFcccccc : 0xFF999999);
|
||||
int labelWidth = font.getStringWidth(label);
|
||||
font.drawString(label, btnX + (buttonSize - labelWidth) / 2, btnY + (buttonSize - font.FONT_HEIGHT) / 2 + 1, textColor);
|
||||
}
|
||||
|
||||
private void drawNavButton(int btnX, int btnY, String arrow, boolean enabled, boolean hovered) {
|
||||
int bgColor = !enabled ? 0xFF2a2a2a : (hovered ? 0xFF505050 : 0xFF3c3c3c);
|
||||
Gui.drawRect(btnX, btnY, btnX + buttonSize, btnY + buttonSize, bgColor);
|
||||
|
||||
int textColor = !enabled ? 0xFF555555 : (hovered ? 0xFFffffff : 0xFFaaaaaa);
|
||||
int arrowWidth = font.getStringWidth(arrow);
|
||||
font.drawString(arrow, btnX + (buttonSize - arrowWidth) / 2, btnY + (buttonSize - font.FONT_HEIGHT) / 2, textColor);
|
||||
}
|
||||
|
||||
private void drawCloseButton(int btnX, int btnY, boolean hovered) {
|
||||
int bgColor = hovered ? 0xFFc75050 : 0xFF3c3c3c;
|
||||
Gui.drawRect(btnX, btnY, btnX + buttonSize, btnY + buttonSize, bgColor);
|
||||
|
||||
int textColor = hovered ? 0xFFffffff : 0xFFaaaaaa;
|
||||
String xChar = "X";
|
||||
int xWidth = font.getStringWidth(xChar);
|
||||
font.drawString(xChar, btnX + (buttonSize - xWidth) / 2, btnY + (buttonSize - font.FONT_HEIGHT) / 2 + 1, textColor);
|
||||
}
|
||||
|
||||
private void drawActionButton(int btnX, int btnY, String label, boolean hovered, boolean enabled) {
|
||||
int btnWidth = font.getStringWidth(label) + 10;
|
||||
int btnHeight = 14;
|
||||
|
||||
int bgColor = !enabled ? 0xFF2a2a2a : (hovered ? 0xFF505050 : 0xFF3c3c3c);
|
||||
Gui.drawRect(btnX, btnY + 1, btnX + btnWidth, btnY + btnHeight + 1, bgColor);
|
||||
Gui.drawRect(btnX, btnY + 1, btnX + btnWidth, btnY + 2, 0xFF555555);
|
||||
Gui.drawRect(btnX, btnY + btnHeight, btnX + btnWidth, btnY + btnHeight + 1, 0xFF2a2a2a);
|
||||
|
||||
int textColor = !enabled ? 0xFF555555 : (hovered ? 0xFFffffff : 0xFFaaaaaa);
|
||||
font.drawString(label, btnX + 5, btnY + (btnHeight - font.FONT_HEIGHT) / 2 + 2, textColor);
|
||||
}
|
||||
|
||||
// ==================== HOVER DETECTION ====================
|
||||
|
||||
private void updateHoverStates(int mouseX, int mouseY) {
|
||||
int rowY = y + padding;
|
||||
int currentX = x + padding;
|
||||
|
||||
int searchFieldEndX = currentX + textFieldWidth;
|
||||
currentX = searchFieldEndX + buttonSpacing + 4;
|
||||
|
||||
String matchInfo = matches.isEmpty() ? "No matches" : (currentMatchIndex + 1) + "/" + matches.size();
|
||||
currentX += font.getStringWidth(matchInfo) + 8;
|
||||
|
||||
int matchCaseX = currentX;
|
||||
hoverMatchCase = isMouseOver(mouseX, mouseY, matchCaseX, rowY, buttonSize, buttonSize);
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
int wholeWordX = currentX;
|
||||
hoverWholeWord = isMouseOver(mouseX, mouseY, wholeWordX, rowY, buttonSize, buttonSize);
|
||||
currentX += buttonSize + buttonSpacing + 4;
|
||||
|
||||
int prevX = currentX;
|
||||
hoverPrev = isMouseOver(mouseX, mouseY, prevX, rowY, buttonSize, buttonSize);
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
int nextX = currentX;
|
||||
hoverNext = isMouseOver(mouseX, mouseY, nextX, rowY, buttonSize, buttonSize);
|
||||
|
||||
int closeX = x + width - buttonSize - padding;
|
||||
hoverClose = isMouseOver(mouseX, mouseY, closeX, rowY, buttonSize, buttonSize);
|
||||
|
||||
if (showReplace) {
|
||||
int replaceRowY = y + barHeight + 2;
|
||||
currentX = x + padding + textFieldWidth + buttonSpacing + 4;
|
||||
|
||||
int replaceWidth = font.getStringWidth("Replace") + 12;
|
||||
hoverReplace = isMouseOver(mouseX, mouseY, currentX, replaceRowY + 1, replaceWidth, 14);
|
||||
currentX += replaceWidth + buttonSpacing;
|
||||
|
||||
int replaceAllWidth = font.getStringWidth("Replace All") + 12;
|
||||
hoverReplaceAll = isMouseOver(mouseX, mouseY, currentX, replaceRowY + 1, replaceAllWidth, 14);
|
||||
currentX += replaceAllWidth + buttonSpacing;
|
||||
|
||||
int excludeWidth = font.getStringWidth("Exclude") + 12;
|
||||
hoverExclude = isMouseOver(mouseX, mouseY, currentX, replaceRowY + 1, excludeWidth, 14);
|
||||
} else {
|
||||
hoverReplace = hoverReplaceAll = hoverExclude = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMouseOver(int mouseX, int mouseY, int bx, int by, int bw, int bh) {
|
||||
return mouseX >= bx && mouseX < bx + bw && mouseY >= by && mouseY < by + bh;
|
||||
}
|
||||
|
||||
// ==================== MOUSE INPUT ====================
|
||||
|
||||
/**
|
||||
* Handle mouse click
|
||||
* @return true if click was consumed
|
||||
*/
|
||||
public boolean mouseClicked(int mouseX, int mouseY, int button) {
|
||||
if (!visible) return false;
|
||||
|
||||
int totalHeight = getTotalHeight();
|
||||
if (mouseY < y || mouseY > y + totalHeight) return false;
|
||||
|
||||
int rowY = y + padding;
|
||||
int currentX = x + padding;
|
||||
|
||||
if (isMouseOver(mouseX, mouseY, currentX, rowY - 2, textFieldWidth, textFieldHeight + 4)) {
|
||||
searchFieldFocused = true;
|
||||
replaceFieldFocused = false;
|
||||
searchCursor = getTextCursorPosition(searchText, mouseX - currentX - 4);
|
||||
searchSelectionStart = searchSelectionEnd = searchCursor;
|
||||
return true;
|
||||
}
|
||||
currentX += textFieldWidth + buttonSpacing + 4;
|
||||
|
||||
String matchInfo = matches.isEmpty() ? "No matches" : (currentMatchIndex + 1) + "/" + matches.size();
|
||||
currentX += font.getStringWidth(matchInfo) + 8;
|
||||
|
||||
if (hoverMatchCase) {
|
||||
matchCase = !matchCase;
|
||||
updateMatches();
|
||||
return true;
|
||||
}
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
if (hoverWholeWord) {
|
||||
wholeWord = !wholeWord;
|
||||
updateMatches();
|
||||
return true;
|
||||
}
|
||||
currentX += buttonSize + buttonSpacing + 4;
|
||||
|
||||
if (hoverPrev && !matches.isEmpty()) {
|
||||
goToPreviousMatch();
|
||||
return true;
|
||||
}
|
||||
currentX += buttonSize + buttonSpacing;
|
||||
|
||||
if (hoverNext && !matches.isEmpty()) {
|
||||
goToNextMatch();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hoverClose) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (showReplace) {
|
||||
int replaceRowY = y + barHeight + 2;
|
||||
|
||||
if (isMouseOver(mouseX, mouseY, x + padding, replaceRowY - 2, textFieldWidth, textFieldHeight + 4)) {
|
||||
searchFieldFocused = false;
|
||||
replaceFieldFocused = true;
|
||||
replaceCursor = getTextCursorPosition(replaceText, mouseX - x - padding - 4);
|
||||
replaceSelectionStart = replaceSelectionEnd = replaceCursor;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hoverReplace && !matches.isEmpty()) {
|
||||
replaceCurrent();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hoverReplaceAll && !matches.isEmpty()) {
|
||||
replaceAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hoverExclude && currentMatchIndex >= 0) {
|
||||
excludeCurrentMatch();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return mouseY < y + totalHeight;
|
||||
}
|
||||
|
||||
private int getTextCursorPosition(String text, int clickX) {
|
||||
if (text.isEmpty() || clickX <= 0) return 0;
|
||||
|
||||
for (int i = 1; i <= text.length(); i++) {
|
||||
int w = font.getStringWidth(text.substring(0, i));
|
||||
if (clickX < w) {
|
||||
int prevW = font.getStringWidth(text.substring(0, i - 1));
|
||||
return clickX < (prevW + w) / 2 ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
return text.length();
|
||||
}
|
||||
|
||||
// ==================== KEYBOARD INPUT ====================
|
||||
|
||||
/**
|
||||
* Handle keyboard input
|
||||
* @return true if input was consumed
|
||||
*/
|
||||
public boolean keyTyped(char c, int keyCode) {
|
||||
if (!visible || (!searchFieldFocused && !replaceFieldFocused)) return false;
|
||||
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == Keyboard.KEY_TAB && showReplace) {
|
||||
searchFieldFocused = !searchFieldFocused;
|
||||
replaceFieldFocused = !replaceFieldFocused;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == Keyboard.KEY_RETURN) {
|
||||
if (replaceFieldFocused && !matches.isEmpty()) {
|
||||
replaceCurrent();
|
||||
} else if (!matches.isEmpty()) {
|
||||
goToNextMatch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (searchFieldFocused) {
|
||||
handleTextFieldInput(c, keyCode, true);
|
||||
} else if (replaceFieldFocused) {
|
||||
handleTextFieldInput(c, keyCode, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleTextFieldInput(char c, int keyCode, boolean isSearchField) {
|
||||
String text = isSearchField ? searchText : replaceText;
|
||||
int cursor = isSearchField ? searchCursor : replaceCursor;
|
||||
int selStart = isSearchField ? searchSelectionStart : replaceSelectionStart;
|
||||
int selEnd = isSearchField ? searchSelectionEnd : replaceSelectionEnd;
|
||||
|
||||
boolean hasSelection = selStart != selEnd;
|
||||
boolean ctrl = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);
|
||||
boolean shift = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
|
||||
|
||||
if (ctrl && keyCode == Keyboard.KEY_A) {
|
||||
selStart = 0;
|
||||
selEnd = text.length();
|
||||
cursor = text.length();
|
||||
}
|
||||
else if (ctrl && keyCode == Keyboard.KEY_C) {
|
||||
if (hasSelection) {
|
||||
String selected = text.substring(Math.min(selStart, selEnd), Math.max(selStart, selEnd));
|
||||
setClipboard(selected);
|
||||
}
|
||||
}
|
||||
else if (ctrl && keyCode == Keyboard.KEY_X) {
|
||||
if (hasSelection) {
|
||||
String selected = text.substring(Math.min(selStart, selEnd), Math.max(selStart, selEnd));
|
||||
setClipboard(selected);
|
||||
text = text.substring(0, Math.min(selStart, selEnd)) + text.substring(Math.max(selStart, selEnd));
|
||||
cursor = Math.min(selStart, selEnd);
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
}
|
||||
else if (ctrl && keyCode == Keyboard.KEY_V) {
|
||||
String clipboard = getClipboard();
|
||||
if (clipboard != null) {
|
||||
clipboard = clipboard.replace("\n", "").replace("\r", "");
|
||||
if (hasSelection) {
|
||||
text = text.substring(0, Math.min(selStart, selEnd)) + clipboard + text.substring(Math.max(selStart, selEnd));
|
||||
cursor = Math.min(selStart, selEnd) + clipboard.length();
|
||||
} else {
|
||||
text = text.substring(0, cursor) + clipboard + text.substring(cursor);
|
||||
cursor += clipboard.length();
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_BACK) {
|
||||
if (hasSelection) {
|
||||
text = text.substring(0, Math.min(selStart, selEnd)) + text.substring(Math.max(selStart, selEnd));
|
||||
cursor = Math.min(selStart, selEnd);
|
||||
} else if (cursor > 0) {
|
||||
text = text.substring(0, cursor - 1) + text.substring(cursor);
|
||||
cursor--;
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_DELETE) {
|
||||
if (hasSelection) {
|
||||
text = text.substring(0, Math.min(selStart, selEnd)) + text.substring(Math.max(selStart, selEnd));
|
||||
cursor = Math.min(selStart, selEnd);
|
||||
} else if (cursor < text.length()) {
|
||||
text = text.substring(0, cursor) + text.substring(cursor + 1);
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_LEFT) {
|
||||
if (shift) {
|
||||
if (cursor > 0) {
|
||||
cursor--;
|
||||
if (selStart == selEnd) {
|
||||
selEnd = cursor + 1;
|
||||
selStart = cursor;
|
||||
} else if (cursor < selStart) {
|
||||
selStart = cursor;
|
||||
} else {
|
||||
selEnd = cursor;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasSelection) {
|
||||
cursor = Math.min(selStart, selEnd);
|
||||
} else if (cursor > 0) {
|
||||
cursor--;
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_RIGHT) {
|
||||
if (shift) {
|
||||
if (cursor < text.length()) {
|
||||
cursor++;
|
||||
if (selStart == selEnd) {
|
||||
selStart = cursor - 1;
|
||||
selEnd = cursor;
|
||||
} else if (cursor > selEnd) {
|
||||
selEnd = cursor;
|
||||
} else {
|
||||
selStart = cursor;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasSelection) {
|
||||
cursor = Math.max(selStart, selEnd);
|
||||
} else if (cursor < text.length()) {
|
||||
cursor++;
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_HOME) {
|
||||
if (shift) {
|
||||
selEnd = selStart == selEnd ? cursor : selEnd;
|
||||
selStart = 0;
|
||||
} else {
|
||||
selStart = selEnd = 0;
|
||||
}
|
||||
cursor = 0;
|
||||
}
|
||||
else if (keyCode == Keyboard.KEY_END) {
|
||||
if (shift) {
|
||||
selStart = selStart == selEnd ? cursor : selStart;
|
||||
selEnd = text.length();
|
||||
} else {
|
||||
selStart = selEnd = text.length();
|
||||
}
|
||||
cursor = text.length();
|
||||
}
|
||||
else if (ChatAllowedCharacters.isAllowedCharacter(c)) {
|
||||
if (hasSelection) {
|
||||
text = text.substring(0, Math.min(selStart, selEnd)) + c + text.substring(Math.max(selStart, selEnd));
|
||||
cursor = Math.min(selStart, selEnd) + 1;
|
||||
} else {
|
||||
text = text.substring(0, cursor) + c + text.substring(cursor);
|
||||
cursor++;
|
||||
}
|
||||
selStart = selEnd = cursor;
|
||||
}
|
||||
|
||||
if (selStart > selEnd) {
|
||||
int temp = selStart;
|
||||
selStart = selEnd;
|
||||
selEnd = temp;
|
||||
}
|
||||
|
||||
if (isSearchField) {
|
||||
searchText = text;
|
||||
searchCursor = cursor;
|
||||
searchSelectionStart = selStart;
|
||||
searchSelectionEnd = selEnd;
|
||||
updateMatches();
|
||||
} else {
|
||||
replaceText = text;
|
||||
replaceCursor = cursor;
|
||||
replaceSelectionStart = selStart;
|
||||
replaceSelectionEnd = selEnd;
|
||||
}
|
||||
}
|
||||
|
||||
private void setClipboard(String text) {
|
||||
try {
|
||||
java.awt.datatransfer.StringSelection selection = new java.awt.datatransfer.StringSelection(text);
|
||||
java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private String getClipboard() {
|
||||
try {
|
||||
return (String) java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getData(java.awt.datatransfer.DataFlavor.stringFlavor);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SEARCH LOGIC ====================
|
||||
|
||||
/**
|
||||
* Update matches based on current search text and options
|
||||
*/
|
||||
public void updateMatches() {
|
||||
matches.clear();
|
||||
excludedMatches.clear();
|
||||
currentMatchIndex = -1;
|
||||
|
||||
if (callback == null || searchText.isEmpty()) return;
|
||||
|
||||
String sourceText = callback.getText();
|
||||
if (sourceText == null || sourceText.isEmpty()) return;
|
||||
|
||||
String searchPattern = searchText;
|
||||
|
||||
if (wholeWord) {
|
||||
searchPattern = "\\b" + Pattern.quote(searchPattern) + "\\b";
|
||||
} else {
|
||||
searchPattern = Pattern.quote(searchPattern);
|
||||
}
|
||||
|
||||
int flags = matchCase ? 0 : Pattern.CASE_INSENSITIVE;
|
||||
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(searchPattern, flags);
|
||||
Matcher matcher = pattern.matcher(sourceText);
|
||||
|
||||
while (matcher.find()) {
|
||||
matches.add(new int[]{matcher.start(), matcher.end()});
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
if (!matches.isEmpty()) {
|
||||
currentMatchIndex = 0;
|
||||
navigateToCurrentMatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to next match
|
||||
*/
|
||||
public void goToNextMatch() {
|
||||
if (matches.isEmpty()) return;
|
||||
|
||||
int startIndex = currentMatchIndex;
|
||||
do {
|
||||
currentMatchIndex = (currentMatchIndex + 1) % matches.size();
|
||||
} while (excludedMatches.contains(currentMatchIndex) && currentMatchIndex != startIndex);
|
||||
|
||||
navigateToCurrentMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to previous match
|
||||
*/
|
||||
public void goToPreviousMatch() {
|
||||
if (matches.isEmpty()) return;
|
||||
|
||||
int startIndex = currentMatchIndex;
|
||||
do {
|
||||
currentMatchIndex = (currentMatchIndex - 1 + matches.size()) % matches.size();
|
||||
} while (excludedMatches.contains(currentMatchIndex) && currentMatchIndex != startIndex);
|
||||
|
||||
navigateToCurrentMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate viewport to current match and select it
|
||||
*/
|
||||
private void navigateToCurrentMatch() {
|
||||
if (callback == null || currentMatchIndex < 0 || currentMatchIndex >= matches.size()) return;
|
||||
|
||||
int[] match = matches.get(currentMatchIndex);
|
||||
callback.scrollToPosition(match[0]);
|
||||
callback.setSelection(match[0], match[1]);
|
||||
}
|
||||
|
||||
// ==================== REPLACE LOGIC ====================
|
||||
|
||||
/**
|
||||
* Replace current match
|
||||
*/
|
||||
public void replaceCurrent() {
|
||||
if (callback == null || currentMatchIndex < 0 || matches.isEmpty()) return;
|
||||
if (excludedMatches.contains(currentMatchIndex)) {
|
||||
goToNextMatch();
|
||||
return;
|
||||
}
|
||||
|
||||
String sourceText = callback.getText();
|
||||
int[] match = matches.get(currentMatchIndex);
|
||||
|
||||
String newText = sourceText.substring(0, match[0]) + replaceText + sourceText.substring(match[1]);
|
||||
callback.setText(newText);
|
||||
|
||||
updateMatches();
|
||||
|
||||
if (!matches.isEmpty()) {
|
||||
int replacementEnd = match[0] + replaceText.length();
|
||||
for (int i = 0; i < matches.size(); i++) {
|
||||
if (matches.get(i)[0] >= replacementEnd) {
|
||||
currentMatchIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
navigateToCurrentMatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all matches
|
||||
*/
|
||||
public void replaceAll() {
|
||||
if (callback == null || matches.isEmpty()) return;
|
||||
|
||||
String sourceText = callback.getText();
|
||||
StringBuilder result = new StringBuilder();
|
||||
int lastEnd = 0;
|
||||
|
||||
for (int i = 0; i < matches.size(); i++) {
|
||||
if (excludedMatches.contains(i)) {
|
||||
continue;
|
||||
}
|
||||
int[] match = matches.get(i);
|
||||
result.append(sourceText, lastEnd, match[0]);
|
||||
result.append(replaceText);
|
||||
lastEnd = match[1];
|
||||
}
|
||||
result.append(sourceText.substring(lastEnd));
|
||||
|
||||
callback.setText(result.toString());
|
||||
updateMatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude current match from replacement
|
||||
*/
|
||||
public void excludeCurrentMatch() {
|
||||
if (currentMatchIndex >= 0 && !excludedMatches.contains(currentMatchIndex)) {
|
||||
excludedMatches.add(currentMatchIndex);
|
||||
goToNextMatch();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== UTILITY ====================
|
||||
|
||||
/**
|
||||
* Get search text for external highlighting
|
||||
*/
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if bar consumes click at position
|
||||
*/
|
||||
public boolean isMouseOverBar(int mouseX, int mouseY) {
|
||||
if (!visible) return false;
|
||||
int totalHeight = getTotalHeight();
|
||||
return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + totalHeight;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,14 @@ public class ScriptEditorKeys extends KeyPresetManager {
|
||||
|
||||
public final KeyPreset FORMAT = add("Format Code").setDefaultState(Keyboard.KEY_F, true, false, false);
|
||||
public final KeyPreset TOGGLE_COMMENT = add("Toggle Comment").setDefaultState(Keyboard.KEY_SLASH, true, false, false);
|
||||
|
||||
// Search/Replace
|
||||
public final KeyPreset SEARCH = add("Search").setDefaultState(Keyboard.KEY_R, true, false, false);
|
||||
public final KeyPreset SEARCH_REPLACE = add("Search & Replace").setDefaultState(Keyboard.KEY_R, true, false, true);
|
||||
|
||||
public ScriptEditorKeys() {
|
||||
super("script_editor");
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user