Skip to content

Commit 2d9bfb8

Browse files
shai-almogclaude
andcommitted
TextArea: grow by content reliably while editing (#4854)
A growByContent multi-line TextArea was clipping the previous row while being edited: text would wrap onto a new visual line but the field would not grow to follow it until focus moved away. The during-editing grow gate (#4741) decided whether to revalidate using estimateLineCount(), a character-column prediction (string length vs getColumns()). The real renderer wraps using font metrics against getWidth() - horizontalPadding, so with a proportional font text wraps a row earlier than the character count predicts. The estimate undercounted, revalidateLater() did not fire, and the field stayed one row short. Leaving the field ran a normal layout that used the accurate getLines(), which is why the size corrected itself. Fix: drop the wrap prediction entirely. Any growth in text length can push content onto an extra wrapped row, so textMightGrowByContent() now triggers on a length increase (or an added hard newline) and lets the layout's getLines() determine the actual size. revalidateLater() coalesces into a single layout per paint, so erring toward an extra revalidate is cheap and, crucially, never under-grows. The gate stays width- and EDT-independent and does not call getLines() from inside setText(), avoiding any layout re-entrancy. The bogus estimateLineCount() helper is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 210c8c3 commit 2d9bfb8

1 file changed

Lines changed: 12 additions & 28 deletions

File tree

CodenameOne/src/com/codename1/ui/TextArea.java

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -618,19 +618,21 @@ && textMightGrowByContent(old, text)) {
618618
}
619619

620620
private boolean textMightGrowByContent(String oldText, String newText) {
621-
int oldNewLines = countNewLines(oldText);
622-
int newNewLines = countNewLines(newText);
623-
if (newNewLines > oldNewLines) {
621+
if (newText == null || oldText == null) {
624622
return true;
625623
}
626-
if (newText == null || oldText == null || newText.length() <= oldText.length()) {
627-
return false;
628-
}
629-
int cols = getColumns();
630-
if (cols > 1) {
631-
return estimateLineCount(newText, cols) > estimateLineCount(oldText, cols);
624+
// An added hard newline always adds a row.
625+
if (countNewLines(newText) > countNewLines(oldText)) {
626+
return true;
632627
}
633-
return false;
628+
// Any growth in length can push the content onto an extra wrapped row.
629+
// We deliberately do NOT try to predict the wrap point here: the previous
630+
// character-column estimate ignored the font and padding, so proportional
631+
// text wrapped a row earlier than predicted and the field clipped the prior
632+
// line while editing (#4854). revalidateLater() coalesces and the layout's
633+
// getLines() (real font metrics, real width) determines the actual size, so
634+
// erring toward an extra revalidate is cheap and never under-grows.
635+
return newText.length() > oldText.length();
634636
}
635637

636638
private int countNewLines(String value) {
@@ -646,24 +648,6 @@ private int countNewLines(String value) {
646648
return count;
647649
}
648650

649-
private int estimateLineCount(String value, int cols) {
650-
if (value == null || value.length() == 0) {
651-
return 1;
652-
}
653-
int lines = 0;
654-
int segmentLength = 0;
655-
for (int i = 0; i < value.length(); i++) {
656-
if (value.charAt(i) == '\n') {
657-
lines += Math.max(1, (segmentLength + cols - 1) / cols);
658-
segmentLength = 0;
659-
} else {
660-
segmentLength++;
661-
}
662-
}
663-
lines += Math.max(1, (segmentLength + cols - 1) / cols);
664-
return lines;
665-
}
666-
667651
/// Convenience method for numeric text fields, returns the value as a number or invalid if the value in the
668652
/// text field isn't a number
669653
///

0 commit comments

Comments
 (0)