Skip to content

Commit

Permalink
Merge pull request #845 from Jugen/line_highlighter
Browse files Browse the repository at this point in the history
Added line highlighter enhancement
  • Loading branch information
Jugen authored Aug 22, 2019
2 parents 1da8760 + b8c713b commit 3f8f305
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 6 deletions.
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

0 comments on commit 3f8f305

Please sign in to comment.