Skip to content

Commit cd7a966

Browse files
Merge pull request #572 from JFormDesigner/caret-positioning-fix
fixed caret positioning in wrapped lines (issue #423)
2 parents cc21b32 + e6c5816 commit cd7a966

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

richtextfx/src/integrationTest/java/org/fxmisc/richtext/keyboard/NavigationTests.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ public void right() {
225225

226226
}
227227

228-
public class MultiLineTests extends InlineCssTextAreaAppTest {
228+
public class MultiLineGridlikeTextTests extends InlineCssTextAreaAppTest {
229229

230230
public final String[] lines = {
231231
"01 02 03 04 05",
@@ -496,6 +496,53 @@ public void end() {
496496

497497
}
498498

499+
public class MultiLineJaggedTextTests extends InlineCssTextAreaAppTest {
500+
501+
String threeLinesOfText = "Some long amount of text to take up a lot of space in the given area.";
502+
503+
@Override
504+
public void start(Stage stage) throws Exception {
505+
super.start(stage);
506+
stage.setWidth(200);
507+
area.replaceText(threeLinesOfText);
508+
area.setWrapText(true);
509+
}
510+
511+
private void waitForMultiLineRegistration() throws TimeoutException {
512+
// When the stage's width changes, TextFlow does not properly handle API calls to a
513+
// multi-line paragraph immediately. So, wait until it correctly responds
514+
// to the stage width change
515+
Future<Void> textFlowIsReady = WaitForAsyncUtils.asyncFx(() -> {
516+
while (area.getParagraphLinesCount(0) != 3) {
517+
sleep(10);
518+
}
519+
});
520+
WaitForAsyncUtils.waitFor(5, TimeUnit.SECONDS, textFlowIsReady);
521+
}
522+
523+
@Test
524+
public void pressingDownMovesCaretToNextLine() throws TimeoutException {
525+
waitForMultiLineRegistration();
526+
527+
area.moveTo(27);
528+
529+
push(DOWN);
530+
531+
assertEquals(57, area.getCaretPosition());
532+
}
533+
534+
@Test
535+
public void pressingUpMovesCaretToPrevLine() throws TimeoutException {
536+
waitForMultiLineRegistration();
537+
538+
area.moveTo(66);
539+
540+
push(UP);
541+
542+
assertEquals(36, area.getCaretPosition());
543+
}
544+
}
545+
499546
public class ViewportTests extends InlineCssTextAreaAppTest {
500547

501548
@Override

richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,36 @@ CharacterHit hitLine(double x, int lineIndex) {
141141
CharacterHit hit(double x, double y) {
142142
HitInfo hit = textLayout().getHitInfo((float) x, (float) y);
143143
int charIdx = hit.getCharIndex();
144+
boolean leading = hit.isLeading();
144145

145146
int lineIdx = getLineIndex((float) y);
146147
if(lineIdx >= getLineCount()) {
147148
return CharacterHit.insertionAt(getCharCount());
148149
}
149150

150-
TextLine line = getLines()[lineIdx];
151+
TextLine[] lines = getLines();
152+
TextLine line = lines[lineIdx];
151153
RectBounds lineBounds = line.getBounds();
152154

155+
// If this is a wrapped paragraph and hit character is at end of hit line,
156+
// make sure that the "character hit" stays at the end of the hit line
157+
// (and not at the beginning of the next line).
158+
if(lines.length > 1 &&
159+
lineIdx < lines.length - 1 &&
160+
charIdx + 1 >= line.getStart() + line.getLength() &&
161+
!leading)
162+
{
163+
leading = true;
164+
}
165+
153166
if(x < lineBounds.getMinX() || x > lineBounds.getMaxX()) {
154-
if(hit.isLeading()) {
167+
if(leading) {
155168
return CharacterHit.insertionAt(charIdx);
156169
} else {
157170
return CharacterHit.insertionAt(charIdx + 1);
158171
}
159172
} else {
160-
if(hit.isLeading()) {
173+
if(leading) {
161174
return CharacterHit.leadingHalfOf(charIdx);
162175
} else {
163176
return CharacterHit.trailingHalfOf(charIdx);

0 commit comments

Comments
 (0)