Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableSet;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
Expand All @@ -47,6 +48,8 @@
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.PathElement;
import javafx.scene.text.TextFlow;

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

@Override
public void lineStart(SelectionPolicy policy) {
int columnPos = virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineStartPosition(caretSelectionBind.getUnderlyingCaret());
moveTo(getCurrentParagraph(), columnPos, policy);
moveTo(getCurrentParagraph(), getCurrentLineStartInParargraph(), policy);
}

@Override
public void lineEnd(SelectionPolicy policy) {
int columnPos = virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineEndPosition(caretSelectionBind.getUnderlyingCaret());
moveTo(getCurrentParagraph(), columnPos, policy);
moveTo(getCurrentParagraph(), getCurrentLineEndInParargraph(), policy);
}

public int getCurrentLineStartInParargraph() {
return virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineStartPosition(caretSelectionBind.getUnderlyingCaret());
}

public int getCurrentLineEndInParargraph() {
return virtualFlow.getCell(getCurrentParagraph()).getNode().getCurrentLineEndPosition(caretSelectionBind.getUnderlyingCaret());
}

private double caretPrevY = -1;
private Selection<PS, SEG, S> lineHighlighter;
private ObjectProperty<Paint> lineHighlighterFill;

/**
* The default fill is "highlighter" yellow. It can also be styled using CSS with:<br>
* <code>.styled-text-area .line-highlighter { -fx-fill: lime; }</code><br>
* CSS selectors from Path, Shape, and Node can also be used.
*/
public void setLineHighlighterFill( Paint highlight )
{
if ( lineHighlighterFill != null && highlight != null ) {
lineHighlighterFill.set( highlight );
}
else {
boolean lineHighlightOn = isLineHighlighterOn();
if ( lineHighlightOn ) setLineHighlighterOn( false );

if ( highlight == null ) lineHighlighterFill = null;
else lineHighlighterFill = new SimpleObjectProperty( highlight );

if ( lineHighlightOn ) setLineHighlighterOn( true );
}
}

public boolean isLineHighlighterOn() {
return lineHighlighter != null && selectionSet.contains( lineHighlighter ) ;
}

/**
* Highlights the line that the main caret is on.<br>
* Line highlighting automatically follows the caret.
*/
public void setLineHighlighterOn( boolean show )
{
if ( show )
{
if ( lineHighlighter != null ) return;

lineHighlighter = new SelectionImpl<>( "line-highlighter", this, path ->
{
if ( lineHighlighterFill == null ) path.setHighlightFill( Color.YELLOW );
else path.highlightFillProperty().bind( lineHighlighterFill );

path.getElements().addListener( (Change<? extends PathElement> chg) ->
{
if ( chg.next() && chg.wasAdded() || chg.wasReplaced() ) {
double maxX = getLayoutBounds().getMaxX();
// The path is limited to the bounds of the text, so here it's altered to the area's width
chg.getAddedSubList().stream().skip(1).limit(2).forEach( ele -> ((LineTo) ele).setX( maxX ) );
// The path can wrap onto another line if enough text is inserted, so here it's trimmed
if ( chg.getAddedSize() > 5 ) path.getElements().remove( 5, 10 );
}
} );
} );

Consumer<Bounds> caretListener = b ->
{
if ( b.getMinY() != caretPrevY && lineHighlighter != null )
{
int p = getCurrentParagraph();
lineHighlighter.selectRange( p, getCurrentLineStartInParargraph(), p, getCurrentLineEndInParargraph() );
caretPrevY = b.getMinY();
}
};

caretBoundsProperty().addListener( (ob,ov,nv) -> nv.ifPresent( caretListener ) );
getCaretBounds().ifPresent( caretListener );
selectionSet.add( lineHighlighter );
}
else if ( lineHighlighter != null ) {
selectionSet.remove( lineHighlighter );
lineHighlighter.deselect();
lineHighlighter = null;
caretPrevY = -1;
}
}

@Override
Expand Down Expand Up @@ -1447,6 +1534,7 @@ private Cell<Paragraph<PS, SEG, S>, ParagraphBox<PS, SEG, S>> createCell(
selection.rangeProperty()
);
SelectionPath path = new SelectionPath(range);
path.getStyleClass().add( selection.getSelectionName() );
selection.configureSelectionPath(path);

box.selectionsProperty().put(selection, path);
Expand Down
4 changes: 3 additions & 1 deletion richtextfx/src/main/java/org/fxmisc/richtext/Selection.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ default void deselect() {
void dispose();

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public class SelectionImpl<PS, SEG, S> implements Selection<PS, SEG, S>, Compara

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

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