fix(CustomMultiLineTextArea): measure text widths at scale factor to stop caret drift when clicking
This commit is contained in:
@@ -86,8 +86,6 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
|
|
||||||
private int cachedRawFontHeight = 0;
|
private int cachedRawFontHeight = 0;
|
||||||
|
|
||||||
private int cachedSpaceWidth = 0;
|
|
||||||
|
|
||||||
private List<String> cachedWrappedLines = null;
|
private List<String> cachedWrappedLines = null;
|
||||||
|
|
||||||
private int cachedTextAreaWidth = -1;
|
private int cachedTextAreaWidth = -1;
|
||||||
@@ -112,7 +110,6 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
public void setFontSize(final int fontSize) {
|
public void setFontSize(final int fontSize) {
|
||||||
if (this.fontSize != fontSize) {
|
if (this.fontSize != fontSize) {
|
||||||
this.cachedLineHeight = 0;
|
this.cachedLineHeight = 0;
|
||||||
this.cachedSpaceWidth = 0;
|
|
||||||
this.cachedFontSize = 0;
|
this.cachedFontSize = 0;
|
||||||
this.cachedRawFontHeight = 0;
|
this.cachedRawFontHeight = 0;
|
||||||
this.cachedWrappedLines = null;
|
this.cachedWrappedLines = null;
|
||||||
@@ -711,7 +708,7 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
return wrapped;
|
return wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int lineWidth = G2DDrawFont.getTextDimensions(line, this.getGraphics(), this.fontSize, 1.0).getX();
|
final int lineWidth = G2DDrawFont.getTextDimensions(line, this.getGraphics(), this.fontSize, this.scaleFactor).getX();
|
||||||
if (lineWidth <= maxWidth) {
|
if (lineWidth <= maxWidth) {
|
||||||
wrapped.add(line);
|
wrapped.add(line);
|
||||||
return wrapped;
|
return wrapped;
|
||||||
@@ -722,7 +719,7 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
|
|
||||||
for (final String word : words) {
|
for (final String word : words) {
|
||||||
final String testLine = currentLine.isEmpty() ? word : currentLine + " " + word;
|
final String testLine = currentLine.isEmpty() ? word : currentLine + " " + word;
|
||||||
final int testWidth = G2DDrawFont.getTextDimensions(testLine, this.getGraphics(), this.fontSize, 1.0).getX();
|
final int testWidth = G2DDrawFont.getTextDimensions(testLine, this.getGraphics(), this.fontSize, this.scaleFactor).getX();
|
||||||
|
|
||||||
if (testWidth <= maxWidth) {
|
if (testWidth <= maxWidth) {
|
||||||
if (!currentLine.isEmpty()) {
|
if (!currentLine.isEmpty()) {
|
||||||
@@ -757,11 +754,12 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
return this.cachedWrappedLines;
|
return this.cachedWrappedLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int screenAreaWidth = toScreenCoord(textAreaWidth);
|
||||||
final List<String> rawLines = getLines();
|
final List<String> rawLines = getLines();
|
||||||
final List<String> wrappedLines = new ArrayList<>();
|
final List<String> wrappedLines = new ArrayList<>();
|
||||||
|
|
||||||
for (final String rawLine : rawLines) {
|
for (final String rawLine : rawLines) {
|
||||||
wrappedLines.addAll(wrapLine(rawLine, textAreaWidth));
|
wrappedLines.addAll(wrapLine(rawLine, screenAreaWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cachedWrappedLines = wrappedLines;
|
this.cachedWrappedLines = wrappedLines;
|
||||||
@@ -830,30 +828,6 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSpaceWidth() {
|
|
||||||
if (this.cachedSpaceWidth > 0 && this.cachedFontSize == this.fontSize) {
|
|
||||||
return this.cachedSpaceWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.getGraphics() == null) {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int width0 = (int) G2DDrawFont.getTextDimensions("MM", this.getGraphics(), this.fontSize, 1.0).getX();
|
|
||||||
final int width1 = (int) G2DDrawFont.getTextDimensions("M M", this.getGraphics(), this.fontSize, 1.0).getX();
|
|
||||||
this.cachedSpaceWidth = width1 - width0;
|
|
||||||
this.cachedFontSize = 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 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();
|
||||||
@@ -861,17 +835,19 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
final int textAreaWidth = getTextAreaWidth();
|
final int textAreaWidth = getTextAreaWidth();
|
||||||
|
|
||||||
// Convert mouse coordinates from screen pixels to logical coordinates
|
// Convert mouse coordinates from screen pixels to logical coordinates
|
||||||
final int logicalMouseX = toLogicalCoord(mouseX);
|
|
||||||
final int logicalMouseY = toLogicalCoord(mouseY);
|
final int logicalMouseY = toLogicalCoord(mouseY);
|
||||||
|
|
||||||
final int clickedWrappedLine = Math.max(0, Math.min(wrappedLines.size() - 1, (Math.max(0, logicalMouseY - CONTENT_MARGIN) + this.scrollOffsetY) / lineHeight));
|
final int clickedWrappedLine = Math.max(0, Math.min(wrappedLines.size() - 1, (Math.max(0, logicalMouseY - CONTENT_MARGIN) + this.scrollOffsetY) / lineHeight));
|
||||||
|
|
||||||
|
final int screenAreaWidth = toScreenCoord(textAreaWidth);
|
||||||
|
final int clickX = mouseX - toScreenCoord(CONTENT_MARGIN);
|
||||||
|
|
||||||
int wrappedLineCount = 0;
|
int wrappedLineCount = 0;
|
||||||
int charOffset = 0;
|
int charOffset = 0;
|
||||||
|
|
||||||
for (int i = 0; i < rawLines.size(); i++) {
|
for (int i = 0; i < rawLines.size(); i++) {
|
||||||
final String rawLine = rawLines.get(i);
|
final String rawLine = rawLines.get(i);
|
||||||
final List<String> wrappedForThisLine = wrapLine(rawLine, textAreaWidth);
|
final List<String> wrappedForThisLine = wrapLine(rawLine, screenAreaWidth);
|
||||||
final int linesForThisRaw = wrappedForThisLine.size();
|
final int linesForThisRaw = wrappedForThisLine.size();
|
||||||
|
|
||||||
if (clickedWrappedLine >= wrappedLineCount && clickedWrappedLine < wrappedLineCount + linesForThisRaw) {
|
if (clickedWrappedLine >= wrappedLineCount && clickedWrappedLine < wrappedLineCount + linesForThisRaw) {
|
||||||
@@ -882,9 +858,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 baseWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX();
|
final int textWidth = G2DDrawFont.getTextAdvance(substr, this.getGraphics(), this.fontSize, this.scaleFactor);
|
||||||
final int textWidth = baseWidth + this.countTrailingSpaces(substr) * this.getSpaceWidth();
|
final int dist = Math.abs(textWidth - clickX);
|
||||||
final int dist = Math.abs(textWidth - (logicalMouseX - CONTENT_MARGIN));
|
|
||||||
if (dist < bestDist) {
|
if (dist < bestDist) {
|
||||||
bestDist = dist;
|
bestDist = dist;
|
||||||
bestPos = j;
|
bestPos = j;
|
||||||
@@ -942,7 +917,7 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> wrappedForThisLine = wrapLine(rawLine, textAreaWidth);
|
final List<String> wrappedForThisLine = wrapLine(rawLine, toScreenCoord(textAreaWidth));
|
||||||
wrappedLineIndex += wrappedForThisLine.size();
|
wrappedLineIndex += wrappedForThisLine.size();
|
||||||
charCount += rawLineLength;
|
charCount += rawLineLength;
|
||||||
}
|
}
|
||||||
@@ -973,12 +948,11 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
return (int) (logicalCoord * this.scaleFactor);
|
return (int) (logicalCoord * this.scaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getLogicalTextWidth(final Graphics2D g2d, final String text) {
|
private int getScreenTextWidth(final Graphics2D g2d, final String text) {
|
||||||
if (text == null || text.isEmpty()) {
|
if (text == null || text.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
final int baseWidth = G2DDrawFont.getTextDimensions(text, g2d, this.fontSize, 1.0).getX();
|
return G2DDrawFont.getTextAdvance(text, g2d, this.fontSize, this.scaleFactor);
|
||||||
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) {
|
||||||
@@ -993,8 +967,11 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
final String line, final int lineSelStart, final int lineSelEnd) {
|
final String line, final int lineSelStart, final int lineSelEnd) {
|
||||||
final var beforeSelection = line.substring(0, lineSelStart);
|
final var beforeSelection = line.substring(0, lineSelStart);
|
||||||
final var throughSelection = line.substring(0, lineSelEnd);
|
final var throughSelection = line.substring(0, lineSelEnd);
|
||||||
final var selectionStartX = x + this.getLogicalTextWidth(g2d, beforeSelection);
|
final int originX = this.toScreenCoord(x);
|
||||||
final var selectionEndX = x + this.getLogicalTextWidth(g2d, throughSelection);
|
final int originY = this.toScreenCoord(y);
|
||||||
|
final int originHeight = this.toScreenCoord(lineHeight);
|
||||||
|
final var selectionStartX = originX + this.getScreenTextWidth(g2d, beforeSelection);
|
||||||
|
final var selectionEndX = originX + this.getScreenTextWidth(g2d, throughSelection);
|
||||||
final var selectionWidth = Math.max(0, selectionEndX - selectionStartX);
|
final var selectionWidth = Math.max(0, selectionEndX - selectionStartX);
|
||||||
|
|
||||||
this.renderTextSegment(g2d, fgColor, x, y, line);
|
this.renderTextSegment(g2d, fgColor, x, y, line);
|
||||||
@@ -1003,10 +980,10 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g2d.setColor(theme.getSelectionPrimary().toColor());
|
g2d.setColor(theme.getSelectionPrimary().toColor());
|
||||||
g2d.fillRect(this.toScreenCoord(selectionStartX), this.toScreenCoord(y), this.toScreenCoord(selectionWidth), this.toScreenCoord(lineHeight));
|
g2d.fillRect(selectionStartX, originY, selectionWidth, originHeight);
|
||||||
|
|
||||||
final var previousClip = g2d.getClip();
|
final var previousClip = g2d.getClip();
|
||||||
g2d.clipRect(this.toScreenCoord(selectionStartX), this.toScreenCoord(y), this.toScreenCoord(selectionWidth), this.toScreenCoord(lineHeight));
|
g2d.clipRect(selectionStartX, originY, selectionWidth, originHeight);
|
||||||
this.renderTextSegment(g2d, theme.getBackgroundNormal().toColor(), x, y, line);
|
this.renderTextSegment(g2d, theme.getBackgroundNormal().toColor(), x, y, line);
|
||||||
g2d.setClip(previousClip);
|
g2d.setClip(previousClip);
|
||||||
}
|
}
|
||||||
@@ -1261,9 +1238,8 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
final String beforeCaret = caretPosInWrappedLine > 0 && caretPosInWrappedLine <= caretWrappedLine.length()
|
final String beforeCaret = caretPosInWrappedLine > 0 && caretPosInWrappedLine <= caretWrappedLine.length()
|
||||||
? 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 int caretAdvance = G2DDrawFont.getTextAdvance(beforeCaret, g2d, this.fontSize, this.scaleFactor);
|
||||||
final int trailingSpaceWidth = this.countTrailingSpaces(beforeCaret) * (int) (this.getSpaceWidth() * this.scaleFactor);
|
final int caretX = caretAdvance + toScreenCoord(CONTENT_MARGIN);
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user