fix(CustomSingleLineTextField, CustomMultiLineTextArea): account for trailing spaces when measuring caret and selection positions

This commit is contained in:
extio-de
2026-10-09 19:13:56 +02:00
parent e5290ae102
commit 221ae0ea7c
2 changed files with 53 additions and 9 deletions

View File

@@ -668,6 +668,14 @@ public class CustomMultiLineTextArea extends Component {
return this.cachedSpaceWidth; return this.cachedSpaceWidth;
} }
private int countTrailingSpaces(final String text) {
int count = 0;
for (int i = text.length() - 1; i >= 0 && text.charAt(i) == ' '; i--) {
count++;
}
return count;
}
private void updateCaretPosition(final int mouseX, final int mouseY) { private void updateCaretPosition(final int mouseX, final int mouseY) {
final List<String> wrappedLines = getWrappedLines(); final List<String> wrappedLines = getWrappedLines();
final List<String> rawLines = getLines(); final List<String> rawLines = getLines();
@@ -696,7 +704,8 @@ public class CustomMultiLineTextArea extends Component {
for (int j = 0; j <= clickedLine.length(); j++) { for (int j = 0; j <= clickedLine.length(); j++) {
final String substr = clickedLine.substring(0, j); final String substr = clickedLine.substring(0, j);
final int textWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX(); final int baseWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX();
final int textWidth = baseWidth + this.countTrailingSpaces(substr) * this.getSpaceWidth();
final int dist = Math.abs(textWidth - (logicalMouseX - CONTENT_MARGIN)); final int dist = Math.abs(textWidth - (logicalMouseX - CONTENT_MARGIN));
if (dist < bestDist) { if (dist < bestDist) {
bestDist = dist; bestDist = dist;
@@ -790,7 +799,8 @@ public class CustomMultiLineTextArea extends Component {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
return 0; return 0;
} }
return G2DDrawFont.getTextDimensions(text, g2d, this.fontSize, 1.0).getX(); final int baseWidth = G2DDrawFont.getTextDimensions(text, g2d, this.fontSize, 1.0).getX();
return baseWidth + this.countTrailingSpaces(text) * this.getSpaceWidth();
} }
private void renderTextSegment(final Graphics2D g2d, final Color color, final int x, final int y, final String text) { private void renderTextSegment(final Graphics2D g2d, final Color color, final int x, final int y, final String text) {
@@ -1074,8 +1084,8 @@ public class CustomMultiLineTextArea extends Component {
? caretWrappedLine.substring(0, caretPosInWrappedLine) ? caretWrappedLine.substring(0, caretPosInWrappedLine)
: ""; : "";
final var textDim = beforeCaret.isEmpty() ? ImmutableCoordI2.create(0, 0) : G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor); final var textDim = beforeCaret.isEmpty() ? ImmutableCoordI2.create(0, 0) : G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor);
final int spaceWidth = beforeCaret.endsWith(" ") ? getSpaceWidth() : 0; final int trailingSpaceWidth = this.countTrailingSpaces(beforeCaret) * (int) (this.getSpaceWidth() * this.scaleFactor);
final int caretX = textDim.getX() + toScreenCoord(CONTENT_MARGIN) + (int) (spaceWidth * this.scaleFactor); final int caretX = textDim.getX() + toScreenCoord(CONTENT_MARGIN) + trailingSpaceWidth;
final int caretY = (int) ((CONTENT_MARGIN + caretWrappedLineIndex * lineHeight - this.scrollOffsetY) * this.scaleFactor); final int caretY = (int) ((CONTENT_MARGIN + caretWrappedLineIndex * lineHeight - this.scrollOffsetY) * this.scaleFactor);
final int caretHeight = (int) (rawFontHeight * this.scaleFactor); final int caretHeight = (int) (rawFontHeight * this.scaleFactor);

View File

@@ -60,6 +60,10 @@ public class CustomSingleLineTextField extends Component {
private int cachedRawFontHeight = 0; private int cachedRawFontHeight = 0;
private int cachedSpaceWidth = 0;
private int cachedSpaceWidthFontSize = 0;
private final ThemeManager themeManager; private final ThemeManager themeManager;
private final Consumer<String> onTextChanged; private final Consumer<String> onTextChanged;
@@ -382,7 +386,8 @@ public class CustomSingleLineTextField extends Component {
for (int j = 0; j <= this.text.length(); j++) { for (int j = 0; j <= this.text.length(); j++) {
final String substr = this.text.substring(0, j); final String substr = this.text.substring(0, j);
final int textWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX(); final int baseWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX();
final int textWidth = baseWidth + this.countTrailingSpaces(substr) * this.getSpaceWidth();
final int dist = Math.abs(textWidth - textX); final int dist = Math.abs(textWidth - textX);
if (dist < bestDist) { if (dist < bestDist) {
bestDist = dist; bestDist = dist;
@@ -400,7 +405,8 @@ public class CustomSingleLineTextField extends Component {
} }
final String beforeCaret = this.text.substring(0, this.caretPosition); final String beforeCaret = this.text.substring(0, this.caretPosition);
final int caretX = G2DDrawFont.getTextDimensions(beforeCaret, this.getGraphics(), this.fontSize, 1.0).getX(); final int baseCaretX = G2DDrawFont.getTextDimensions(beforeCaret, this.getGraphics(), this.fontSize, 1.0).getX();
final int caretX = baseCaretX + this.countTrailingSpaces(beforeCaret) * this.getSpaceWidth();
final int visibleWidth = getVisibleWidth(); final int visibleWidth = getVisibleWidth();
if (caretX < this.scrollOffsetX) { if (caretX < this.scrollOffsetX) {
@@ -418,6 +424,30 @@ public class CustomSingleLineTextField extends Component {
return (int) (this.getWidth() / this.scaleFactor); return (int) (this.getWidth() / this.scaleFactor);
} }
private int getSpaceWidth() {
if (this.cachedSpaceWidth > 0 && this.cachedSpaceWidthFontSize == this.fontSize) {
return this.cachedSpaceWidth;
}
if (this.getGraphics() == null) {
return 10;
}
final int widthWithoutSpace = G2DDrawFont.getTextDimensions("MM", this.getGraphics(), this.fontSize, 1.0).getX();
final int widthWithSpace = G2DDrawFont.getTextDimensions("M M", this.getGraphics(), this.fontSize, 1.0).getX();
this.cachedSpaceWidth = widthWithSpace - widthWithoutSpace;
this.cachedSpaceWidthFontSize = this.fontSize;
return this.cachedSpaceWidth;
}
private int countTrailingSpaces(final String text) {
int count = 0;
for (int i = text.length() - 1; i >= 0 && text.charAt(i) == ' '; i--) {
count++;
}
return count;
}
private int toLogicalCoord(final int screenCoord) { private int toLogicalCoord(final int screenCoord) {
return (int) (screenCoord / this.scaleFactor); return (int) (screenCoord / this.scaleFactor);
} }
@@ -574,8 +604,11 @@ public class CustomSingleLineTextField extends Component {
final String selected = this.text.substring(selStart, selEnd); final String selected = this.text.substring(selStart, selEnd);
final String afterSel = this.text.substring(selEnd); final String afterSel = this.text.substring(selEnd);
final int beforeSelWidth = G2DDrawFont.getTextDimensions(beforeSel, g2d, this.fontSize, this.scaleFactor).getX(); final int baseBeforeSelWidth = G2DDrawFont.getTextDimensions(beforeSel, g2d, this.fontSize, this.scaleFactor).getX();
final int selWidth = G2DDrawFont.getTextDimensions(selected, g2d, this.fontSize, this.scaleFactor).getX(); final int baseSelWidth = G2DDrawFont.getTextDimensions(selected, g2d, this.fontSize, this.scaleFactor).getX();
final int spaceWidthScaled = (int) (this.getSpaceWidth() * this.scaleFactor);
final int beforeSelWidth = baseBeforeSelWidth + this.countTrailingSpaces(beforeSel) * spaceWidthScaled;
final int selWidth = baseSelWidth + this.countTrailingSpaces(selected) * spaceWidthScaled;
final int drawOffsetX = TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor); final int drawOffsetX = TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor);
if (!beforeSel.isEmpty()) { if (!beforeSel.isEmpty()) {
@@ -615,7 +648,8 @@ public class CustomSingleLineTextField extends Component {
final var textDim = beforeCaret.isEmpty() final var textDim = beforeCaret.isEmpty()
? ImmutableCoordI2.create(0, 0) ? ImmutableCoordI2.create(0, 0)
: G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor); : G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor);
final int caretX = textDim.getX() + TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor); final int trailingSpaceWidth = this.countTrailingSpaces(beforeCaret) * (int) (this.getSpaceWidth() * this.scaleFactor);
final int caretX = textDim.getX() + trailingSpaceWidth + TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor);
final int caretY = (int) (textY * this.scaleFactor); final int caretY = (int) (textY * this.scaleFactor);
final int caretHeight = (int) (rawFontHeight * this.scaleFactor); final int caretHeight = (int) (rawFontHeight * this.scaleFactor);