mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 15:27:29 +00:00
Enter/Backspace/Tab/Up/Down now auto scroll to the cursor's position !!!
This commit is contained in:
1 parent
492bec21c3
commit
8a5e1fb062
2 files changed
+143
-74
No files matched your search
@@ -56,8 +56,8 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
public List<GuiScriptTextArea.UndoData> redoList = new ArrayList();
|
||||
public boolean undoing = false;
|
||||
private long lastClicked = 0L;
|
||||
// Line number gutter width (left margin for line numbers)
|
||||
private static int LINE_NUMBER_GUTTER_WIDTH = 25;
|
||||
// Line number gutter width (left margin for line numbers) - dynamically calculated
|
||||
private static int LINE_NUMBER_GUTTER_WIDTH = 25;
|
||||
// private static TrueTypeFont font = new TrueTypeFont(new Font("Arial Unicode MS", Font.PLAIN, ConfigClient.FontSize), 1);
|
||||
|
||||
public GuiScriptTextArea(GuiScreen guiScreen, int id, int x, int y, int width, int height, String text) {
|
||||
@@ -104,7 +104,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
int scissorH = this.height * scaleFactor;
|
||||
GL11.glScissor(scissorX, scissorY, scissorW, scissorH);
|
||||
|
||||
container.visibleLines = height / container.lineHeight;
|
||||
container.visibleLines = height / container.lineHeight-1;
|
||||
|
||||
int maxScroll = Math.max(0, this.container.linesCount - container.visibleLines);
|
||||
if (listener instanceof GuiNPCInterface) {
|
||||
@@ -181,6 +181,10 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
scrollVelocity = 0.0;
|
||||
}
|
||||
}
|
||||
// Ensure scrollPos stays in valid bounds
|
||||
if (scrollPos < 0) scrollPos = 0;
|
||||
if (scrollPos > maxScroll) scrollPos = maxScroll;
|
||||
|
||||
// Update integer scrolledLine for compatibility
|
||||
scrolledLine = Math.max(0, Math.min((int) Math.floor(scrollPos), maxScroll));
|
||||
|
||||
@@ -596,7 +600,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
LineData data = (LineData) list.get(i);
|
||||
if (i >= this.scrolledLine && i < this.scrolledLine + this.container.visibleLines) {
|
||||
if (i >= this.scrolledLine && i <= this.scrolledLine + this.container.visibleLines) {
|
||||
int yPos = (i - this.scrolledLine) * this.container.lineHeight;
|
||||
if (yMouse >= yPos && yMouse < yPos + this.container.lineHeight) {
|
||||
int lineWidth = 0;
|
||||
@@ -625,55 +629,58 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
return this.container.text.length();
|
||||
}
|
||||
|
||||
// Ensure the cursor's line is visible by updating the smooth scroll target
|
||||
// Mimics IntelliJ IDEA behavior: when pressing Enter, maintain the cursor's relative Y position
|
||||
// in the viewport by scrolling forward to compensate for the newly added line.
|
||||
private void ensureCursorVisible() {
|
||||
if (container == null || container.lines == null || container.lines.isEmpty()) return;
|
||||
|
||||
// Find the cursor's current line index
|
||||
int lineIdx = 0;
|
||||
// Find which line the cursor is on (0-indexed)
|
||||
private int getCursorLineIndex() {
|
||||
if (container == null || container.lines == null || container.lines.isEmpty()) return 0;
|
||||
for (int i = 0; i < container.lines.size(); i++) {
|
||||
LineData ld = container.lines.get(i);
|
||||
if (cursorPosition >= ld.start && cursorPosition <= ld.end) {
|
||||
lineIdx = i;
|
||||
break;
|
||||
// For last line, use <= since end doesn't include +1 for newline
|
||||
// For other lines, use < since end includes the newline position
|
||||
boolean isLastLine = (i == container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (cursorPosition >= ld.start && cursorPosition <= ld.end)
|
||||
: (cursorPosition >= ld.start && cursorPosition < ld.end);
|
||||
if (isOnLine) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return container.lines.size() - 1;
|
||||
}
|
||||
|
||||
// Check if cursor is on a specific line, handling the last line specially
|
||||
// (last line doesn't have trailing newline, so use <= for end boundary)
|
||||
private boolean isCursorOnLine(int lineIndex, LineData line) {
|
||||
boolean isLastLine = (lineIndex == container.lines.size() - 1);
|
||||
return isLastLine
|
||||
? (cursorPosition >= line.start && cursorPosition <= line.end)
|
||||
: (cursorPosition >= line.start && cursorPosition < line.end);
|
||||
}
|
||||
|
||||
// Scroll viewport to keep cursor visible (minimal adjustment, like IntelliJ)
|
||||
// Only scrolls if cursor is outside the visible area
|
||||
private void scrollToCursor() {
|
||||
if (container == null || container.lines == null || container.lines.isEmpty()) return;
|
||||
|
||||
int lineIdx = getCursorLineIndex();
|
||||
int visible = Math.max(1, container.visibleLines);
|
||||
int maxScroll = Math.max(0, container.linesCount - visible);
|
||||
|
||||
// Calculate the cursor line's relative position in the current viewport (0 = top, visible-1 = bottom)
|
||||
double relativePos = lineIdx - scrollPos;
|
||||
int firstVisible = scrolledLine;
|
||||
int lastFullyVisible = scrolledLine + visible;
|
||||
|
||||
// If cursor is already visible in the viewport
|
||||
if (relativePos >= 0 && relativePos < visible) {
|
||||
// When Enter is pressed, a new line is inserted and cursor advances by 1.
|
||||
// To maintain the same visual Y position, scroll forward by 1 line.
|
||||
// This keeps the cursor at the same relative position while content scrolls up.
|
||||
double newScroll = scrollPos + 1.0;
|
||||
newScroll = Math.max(0, Math.min(newScroll, maxScroll));
|
||||
|
||||
targetScroll = newScroll;
|
||||
scrollPos = newScroll;
|
||||
scrolledLine = Math.max(0, Math.min((int) Math.floor(scrollPos), maxScroll));
|
||||
scrollVelocity = 0.0;
|
||||
} else if (relativePos < 0) {
|
||||
// Cursor moved above the viewport; scroll up to show it near the top
|
||||
double newScroll = Math.max(0, lineIdx - 2); // keep 2 lines above for context
|
||||
targetScroll = newScroll;
|
||||
scrollPos = newScroll;
|
||||
scrolledLine = Math.max(0, Math.min((int) Math.floor(scrollPos), maxScroll));
|
||||
scrollVelocity = 0.0;
|
||||
} else {
|
||||
// Cursor is below viewport; scroll down to show it
|
||||
double newScroll = Math.max(0, Math.min(lineIdx - visible + 3, maxScroll));
|
||||
targetScroll = newScroll;
|
||||
scrollPos = newScroll;
|
||||
scrolledLine = Math.max(0, Math.min((int) Math.floor(scrollPos), maxScroll));
|
||||
scrollVelocity = 0.0;
|
||||
if (lineIdx < firstVisible) {
|
||||
// Cursor is above viewport - scroll up just enough to show it
|
||||
targetScroll = lineIdx;
|
||||
} else if (lineIdx > lastFullyVisible) {
|
||||
// Cursor is below viewport - scroll down just enough to show it
|
||||
targetScroll = Math.min(lineIdx - visible, maxScroll);
|
||||
}
|
||||
// If cursor is visible, don't change scroll
|
||||
}
|
||||
|
||||
// Legacy method name for compatibility
|
||||
private void ensureCursorVisible() {
|
||||
scrollToCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -727,10 +734,12 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
}
|
||||
if (i == Keyboard.KEY_UP) {
|
||||
setCursor(cursorUp(), GuiScreen.isShiftKeyDown());
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_DOWN) {
|
||||
setCursor(cursorDown(), GuiScreen.isShiftKeyDown());
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_DELETE) {
|
||||
@@ -787,6 +796,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
String s = getSelectionBeforeText();
|
||||
setText(s + getSelectionAfterText());
|
||||
endSelection = cursorPosition = startSelection;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -797,8 +807,13 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
// Find current line for indent-aware backspace
|
||||
LineData curr = null;
|
||||
for (LineData line : container.lines) {
|
||||
if (cursorPosition >= line.start && cursorPosition < line.end) {
|
||||
for (int idx = 0; idx < container.lines.size(); idx++) {
|
||||
LineData line = container.lines.get(idx);
|
||||
boolean isLastLine = (idx == container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (cursorPosition >= line.start && cursorPosition <= line.end)
|
||||
: (cursorPosition >= line.start && cursorPosition < line.end);
|
||||
if (isOnLine) {
|
||||
curr = line;
|
||||
break;
|
||||
}
|
||||
@@ -830,6 +845,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
// Cursor goes to end of previous line (at the newline position, which is now end of prev line content)
|
||||
int newCursor = Math.max(0, curr.start - 1);
|
||||
endSelection = cursorPosition = startSelection = newCursor;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
} else {
|
||||
// Line has content: merge with previous line
|
||||
@@ -857,6 +873,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
setText(before + spacer + content);
|
||||
int newCursor = before.length() + spacer.length();
|
||||
endSelection = cursorPosition = startSelection = newCursor;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -876,6 +893,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
setText(before + after);
|
||||
startSelection -= 1;
|
||||
endSelection = cursorPosition = startSelection;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -886,6 +904,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
startSelection--;
|
||||
setText(s + getSelectionAfterText());
|
||||
endSelection = cursorPosition = startSelection;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (this.isKeyComboCtrlX(i)) {
|
||||
@@ -894,7 +913,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
String s = getSelectionBeforeText();
|
||||
setText(s + getSelectionAfterText());
|
||||
endSelection = startSelection = cursorPosition = s.length();
|
||||
|
||||
scrollToCursor();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -906,6 +925,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
}
|
||||
if (this.isKeyComboCtrlV(i)) {
|
||||
addText(NoppesStringUtils.getClipboardContents());
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_Z && isCtrlKeyDown()) {
|
||||
@@ -917,6 +937,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
setText(data.text);
|
||||
endSelection = startSelection = cursorPosition = data.cursorPosition;
|
||||
undoing = false;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_Y && isCtrlKeyDown()) {
|
||||
@@ -928,6 +949,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
setText(data.text);
|
||||
endSelection = startSelection = cursorPosition = data.cursorPosition;
|
||||
undoing = false;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_TAB) {
|
||||
@@ -937,6 +959,8 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
} else {
|
||||
handleTab();
|
||||
}
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (i == Keyboard.KEY_F && isCtrlKeyDown()) {
|
||||
formatText();
|
||||
@@ -956,7 +980,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
String leadingSegment = firstNewline == -1 ? after : after.substring(0, firstNewline);
|
||||
if (leadingSegment.trim().length() > 0) {
|
||||
addText("\n" + childIndent);
|
||||
ensureCursorVisible();
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -999,17 +1023,17 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
if (hasMatchingCloseSameIndent) {
|
||||
addText("\n" + childIndent);
|
||||
ensureCursorVisible();
|
||||
scrollToCursor();
|
||||
} else {
|
||||
String insert = "\n" + childIndent + "\n" + indent + "}";
|
||||
setText(before + insert + after);
|
||||
int newCursor = before.length() + 1 + childIndent.length();
|
||||
startSelection = endSelection = cursorPosition = newCursor;
|
||||
ensureCursorVisible();
|
||||
scrollToCursor();
|
||||
}
|
||||
} else {
|
||||
addText(Character.toString('\n') + getAutoIndentForEnter());
|
||||
ensureCursorVisible();
|
||||
scrollToCursor();
|
||||
}
|
||||
}
|
||||
if (ChatAllowedCharacters.isAllowedCharacter(c)) {
|
||||
@@ -1020,31 +1044,37 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
if ((c == ')' || c == ']' || c == '"' || c == '\'' ) && after.length() > 0 && after.charAt(0) == c) {
|
||||
// move cursor forward over existing closer
|
||||
startSelection = endSelection = cursorPosition = before.length() + 1;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
setText(before + "\"\"" + after);
|
||||
startSelection = endSelection = cursorPosition = before.length() + 1;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (c == '\'') {
|
||||
setText(before + "''" + after);
|
||||
startSelection = endSelection = cursorPosition = before.length() + 1;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (c == '[') {
|
||||
setText(before + "[]" + after);
|
||||
startSelection = endSelection = cursorPosition = before.length() + 1;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
if (c == '(') {
|
||||
setText(before + "()" + after);
|
||||
startSelection = endSelection = cursorPosition = before.length() + 1;
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
addText(Character.toString(c));
|
||||
scrollToCursor();
|
||||
}
|
||||
|
||||
if (i == Keyboard.KEY_SLASH && isCtrlKeyDown()) {
|
||||
@@ -1483,8 +1513,13 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
private void handleTab() {
|
||||
LineData currentLine = null;
|
||||
for (LineData line : this.container.lines) {
|
||||
if (this.cursorPosition >= line.start && this.cursorPosition < line.end) {
|
||||
for (int i = 0; i < this.container.lines.size(); i++) {
|
||||
LineData line = this.container.lines.get(i);
|
||||
boolean isLastLine = (i == this.container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (this.cursorPosition >= line.start && this.cursorPosition <= line.end)
|
||||
: (this.cursorPosition >= line.start && this.cursorPosition < line.end);
|
||||
if (isOnLine) {
|
||||
currentLine = line;
|
||||
break;
|
||||
}
|
||||
@@ -1542,8 +1577,13 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
|
||||
private void handleShiftTab() {
|
||||
LineData currentLine = null;
|
||||
for (LineData line : this.container.lines) {
|
||||
if (this.cursorPosition >= line.start && this.cursorPosition < line.end) {
|
||||
for (int i = 0; i < this.container.lines.size(); i++) {
|
||||
LineData line = this.container.lines.get(i);
|
||||
boolean isLastLine = (i == this.container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (this.cursorPosition >= line.start && this.cursorPosition <= line.end)
|
||||
: (this.cursorPosition >= line.start && this.cursorPosition < line.end);
|
||||
if (isOnLine) {
|
||||
currentLine = line;
|
||||
break;
|
||||
}
|
||||
@@ -1626,16 +1666,24 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
private int cursorUp() {
|
||||
for (int i = 0; i < this.container.lines.size(); ++i) {
|
||||
LineData data = (LineData) this.container.lines.get(i);
|
||||
if (this.cursorPosition >= data.start && this.cursorPosition < data.end) {
|
||||
// For last line, use <= since end doesn't include +1 for newline
|
||||
// For other lines, use < since end includes the newline position
|
||||
boolean isLastLine = (i == this.container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (this.cursorPosition >= data.start && this.cursorPosition <= data.end)
|
||||
: (this.cursorPosition >= data.start && this.cursorPosition < data.end);
|
||||
if (isOnLine) {
|
||||
if (i == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int column = this.cursorPosition - data.start;
|
||||
int column = Math.min(this.cursorPosition - data.start, data.text.length());
|
||||
LineData target = this.container.lines.get(i - 1);
|
||||
int targetPos = this.getSelectionPos(this.x + LINE_NUMBER_GUTTER_WIDTH + 1 + ClientProxy.Font.width(data.text.substring(0, column)), this.y + 1 + (i - 1 - this.scrolledLine) * this.container.lineHeight);
|
||||
// Calculate target position on the line above, maintaining column if possible
|
||||
int targetColumn = Math.min(column, target.text.length());
|
||||
int targetIndent = getLineIndent(target.text);
|
||||
int minPos = target.start + Math.min(targetIndent, Math.max(0, target.text.length()));
|
||||
int minPos = target.start + Math.min(targetIndent, target.text.length());
|
||||
int targetPos = target.start + targetColumn;
|
||||
return Math.max(minPos, targetPos);
|
||||
}
|
||||
}
|
||||
@@ -1648,12 +1696,25 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
return cursorPosition;
|
||||
for (int i = 0; i < this.container.lines.size(); ++i) {
|
||||
LineData data = (LineData) this.container.lines.get(i);
|
||||
if (this.cursorPosition >= data.start && this.cursorPosition < data.end) {
|
||||
int column = this.cursorPosition - data.start;
|
||||
LineData target = this.container.lines.get(Math.min(i + 1, this.container.lines.size() - 1));
|
||||
int targetPos = this.getSelectionPos(this.x + LINE_NUMBER_GUTTER_WIDTH + 1 + ClientProxy.Font.width(data.text.substring(0, column)), this.y + 1 + (i + 1 - this.scrolledLine) * this.container.lineHeight);
|
||||
// For last line, use <= since end doesn't include +1 for newline
|
||||
// For other lines, use < since end includes the newline position
|
||||
boolean isLastLine = (i == this.container.lines.size() - 1);
|
||||
boolean isOnLine = isLastLine
|
||||
? (this.cursorPosition >= data.start && this.cursorPosition <= data.end)
|
||||
: (this.cursorPosition >= data.start && this.cursorPosition < data.end);
|
||||
if (isOnLine) {
|
||||
// If already on last line, stay at current position
|
||||
if (i >= this.container.lines.size() - 1) {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
int column = Math.min(this.cursorPosition - data.start, data.text.length());
|
||||
LineData target = this.container.lines.get(i + 1);
|
||||
// Calculate target position on the line below, maintaining column if possible
|
||||
int targetColumn = Math.min(column, target.text.length());
|
||||
int targetIndent = getLineIndent(target.text);
|
||||
int minPos = target.start + Math.min(targetIndent, Math.max(0, target.text.length()));
|
||||
int minPos = target.start + Math.min(targetIndent, target.text.length());
|
||||
int targetPos = target.start + targetColumn;
|
||||
return Math.max(minPos, targetPos);
|
||||
}
|
||||
}
|
||||
@@ -1750,14 +1811,17 @@ public class GuiScriptTextArea extends GuiNpcTextField {
|
||||
this.container.formatCodeText();
|
||||
}
|
||||
|
||||
// Ensure scrolledLine stays in bounds and snap smooth scroll to avoid perceived lag
|
||||
// Ensure scroll state stays in bounds after text change
|
||||
int maxScroll = Math.max(0, this.container.linesCount - this.container.visibleLines);
|
||||
if (this.scrolledLine > maxScroll) {
|
||||
this.scrolledLine = maxScroll;
|
||||
}
|
||||
// Immediately update fractional scroll targets so new content appears without delay
|
||||
this.targetScroll = this.scrolledLine;
|
||||
this.scrollPos = this.scrolledLine;
|
||||
if (this.targetScroll > maxScroll) {
|
||||
this.targetScroll = maxScroll;
|
||||
}
|
||||
if (this.scrollPos > maxScroll) {
|
||||
this.scrollPos = maxScroll;
|
||||
}
|
||||
|
||||
clampSelectionBounds();
|
||||
|
||||
|
||||
Reference in new issue
Block a user