Skip to content

Commit b8c713b

Browse files
author
Jurgen
committed
Added line highlighter enhancement
1 parent 1da8760 commit b8c713b

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

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

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import javafx.beans.property.SimpleObjectProperty;
2626
import javafx.beans.value.ObservableValue;
2727
import javafx.collections.FXCollections;
28+
import javafx.collections.ListChangeListener.Change;
2829
import javafx.collections.ObservableSet;
2930
import javafx.css.CssMetaData;
3031
import javafx.css.PseudoClass;
@@ -47,6 +48,8 @@
4748
import javafx.scene.layout.Region;
4849
import javafx.scene.paint.Color;
4950
import javafx.scene.paint.Paint;
51+
import javafx.scene.shape.LineTo;
52+
import javafx.scene.shape.PathElement;
5053
import javafx.scene.text.TextFlow;
5154

5255
import org.fxmisc.flowless.Cell;
@@ -1091,14 +1094,98 @@ public void requestFollowCaret() {
10911094

10921095
@Override
10931096
public void lineStart(SelectionPolicy policy) {
1094-
int columnPos = virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineStartPosition(caretSelectionBind.getUnderlyingCaret());
1095-
moveTo(getCurrentParagraph(), columnPos, policy);
1097+
moveTo(getCurrentParagraph(), getCurrentLineStartInParargraph(), policy);
10961098
}
10971099

10981100
@Override
10991101
public void lineEnd(SelectionPolicy policy) {
1100-
int columnPos = virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineEndPosition(caretSelectionBind.getUnderlyingCaret());
1101-
moveTo(getCurrentParagraph(), columnPos, policy);
1102+
moveTo(getCurrentParagraph(), getCurrentLineEndInParargraph(), policy);
1103+
}
1104+
1105+
public int getCurrentLineStartInParargraph() {
1106+
return virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineStartPosition(caretSelectionBind.getUnderlyingCaret());
1107+
}
1108+
1109+
public int getCurrentLineEndInParargraph() {
1110+
return virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineEndPosition(caretSelectionBind.getUnderlyingCaret());
1111+
}
1112+
1113+
private double caretPrevY = -1;
1114+
private Selection<PS, SEG, S> lineHighlighter;
1115+
private ObjectProperty<Paint> lineHighlighterFill;
1116+
1117+
/**
1118+
* The default fill is "highlighter" yellow. It can also be styled using CSS with:<br>
1119+
* <code>.styled-text-area .line-highlighter { -fx-fill: lime; }</code><br>
1120+
* CSS selectors from Path, Shape, and Node can also be used.
1121+
*/
1122+
public void setLineHighlighterFill( Paint highlight )
1123+
{
1124+
if ( lineHighlighterFill != null && highlight != null ) {
1125+
lineHighlighterFill.set( highlight );
1126+
}
1127+
else {
1128+
boolean lineHighlightOn = isLineHighlighterOn();
1129+
if ( lineHighlightOn ) setLineHighlighterOn( false );
1130+
1131+
if ( highlight == null ) lineHighlighterFill = null;
1132+
else lineHighlighterFill = new SimpleObjectProperty( highlight );
1133+
1134+
if ( lineHighlightOn ) setLineHighlighterOn( true );
1135+
}
1136+
}
1137+
1138+
public boolean isLineHighlighterOn() {
1139+
return lineHighlighter != null && selectionSet.contains( lineHighlighter ) ;
1140+
}
1141+
1142+
/**
1143+
* Highlights the line that the main caret is on.<br>
1144+
* Line highlighting automatically follows the caret.
1145+
*/
1146+
public void setLineHighlighterOn( boolean show )
1147+
{
1148+
if ( show )
1149+
{
1150+
if ( lineHighlighter != null ) return;
1151+
1152+
lineHighlighter = new SelectionImpl<>( "line-highlighter", this, path ->
1153+
{
1154+
if ( lineHighlighterFill == null ) path.setHighlightFill( Color.YELLOW );
1155+
else path.highlightFillProperty().bind( lineHighlighterFill );
1156+
1157+
path.getElements().addListener( (Change<? extends PathElement> chg) ->
1158+
{
1159+
if ( chg.next() && chg.wasAdded() || chg.wasReplaced() ) {
1160+
double maxX = getLayoutBounds().getMaxX();
1161+
// The path is limited to the bounds of the text, so here it's altered to the area's width
1162+
chg.getAddedSubList().stream().skip(1).limit(2).forEach( ele -> ((LineTo) ele).setX( maxX ) );
1163+
// The path can wrap onto another line if enough text is inserted, so here it's trimmed
1164+
if ( chg.getAddedSize() > 5 ) path.getElements().remove( 5, 10 );
1165+
}
1166+
} );
1167+
} );
1168+
1169+
Consumer<Bounds> caretListener = b ->
1170+
{
1171+
if ( b.getMinY() != caretPrevY && lineHighlighter != null )
1172+
{
1173+
int p = getCurrentParagraph();
1174+
lineHighlighter.selectRange( p, getCurrentLineStartInParargraph(), p, getCurrentLineEndInParargraph() );
1175+
caretPrevY = b.getMinY();
1176+
}
1177+
};
1178+
1179+
caretBoundsProperty().addListener( (ob,ov,nv) -> nv.ifPresent( caretListener ) );
1180+
getCaretBounds().ifPresent( caretListener );
1181+
selectionSet.add( lineHighlighter );
1182+
}
1183+
else if ( lineHighlighter != null ) {
1184+
selectionSet.remove( lineHighlighter );
1185+
lineHighlighter.deselect();
1186+
lineHighlighter = null;
1187+
caretPrevY = -1;
1188+
}
11021189
}
11031190

11041191
@Override
@@ -1447,6 +1534,7 @@ private Cell<Paragraph<PS, SEG, S>, ParagraphBox<PS, SEG, S>> createCell(
14471534
selection.rangeProperty()
14481535
);
14491536
SelectionPath path = new SelectionPath(range);
1537+
path.getStyleClass().add( selection.getSelectionName() );
14501538
selection.configureSelectionPath(path);
14511539

14521540
box.selectionsProperty().put(selection, path);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ default void deselect() {
189189
void dispose();
190190

191191
/**
192-
* Gets the name of this selection. Each selection in an area must have a unique name.
192+
* Gets the name of this selection. Each selection in an area must have a unique name.<br>
193+
* The name is also used as a StyleClass, so the Selection can be styled using CSS selectors
194+
* from Path, Shape, and Node eg:<br>.styled-text-area .my-selection { -fx-fill: lime; }
193195
*/
194196
String getSelectionName();
195197

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public class SelectionImpl<PS, SEG, S> implements Selection<PS, SEG, S>, Compara
116116

117117
/**
118118
* Creates a selection with both the start and end position at 0.
119+
* @param name must be unique and is also used as a StyleClass for
120+
* configuration via CSS using selectors from Path, Shape, and Node.
119121
*/
120122
public SelectionImpl(String name, GenericStyledArea<PS, SEG, S> area) {
121123
this(name, area, 0, 0);
@@ -130,7 +132,8 @@ public SelectionImpl(String name, GenericStyledArea<PS, SEG, S> area, Consumer<S
130132
}
131133

132134
/**
133-
* Creates a selection
135+
* Creates a selection. Name must be unique and is also used as a StyleClass
136+
* for configuration via CSS using selectors from Path, Shape, and Node.
134137
*/
135138
public SelectionImpl(String name, GenericStyledArea<PS, SEG, S> area, int startPosition, int endPosition) {
136139
this(name, area, new IndexRange(startPosition, endPosition), area.beingUpdatedProperty());

0 commit comments

Comments
 (0)