blob: 5e4540523c0ddbc9bc055246569a02d6df5e9744 [file] [log] [blame]
package org.eclipse.fx.ui.controls.styledtext_ng;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.fx.core.Range;
import org.eclipse.fx.core.Subscription;
import org.eclipse.fx.ui.controls.styledtext.StyledString;
import org.eclipse.fx.ui.controls.styledtext.StyledStringSegment;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableBooleanProperty;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
public class StyledLine extends Region {
private final StyledLineRenderer renderer;
private final List<Subscription> ranges = new ArrayList<>();
public StyledLine(StyledLineRendererFactory factory) {
this.renderer = factory.createRenderer();
Node node = renderer.getNode();
getChildren().add(node);
}
@Override
protected void layoutChildren() {
getManagedChildren().forEach( c -> c.resizeRelocate(0, 0, c.prefWidth(-1), c.prefHeight(-1)));
}
public void setStyledString(StyledString string) {
Map<List<String>, SegmentNode> map = new HashMap<>();
renderer.combinedAction( () -> {
int idx = 0;
ranges.forEach(Subscription::dispose);
for( StyledStringSegment s : string.getSegmentList() ) {
SegmentNode node = map.computeIfAbsent(s.getStyleClass(), l -> new SegmentNode(l,this.renderer));
this.ranges.add(node.addRange(new Range(idx, idx += s.getText().length())));
}
this.renderer.setText(string.toString().toCharArray());
});
getChildren().addAll( map.values() );
}
public void setFont(String family, double size) {
this.renderer.setFont(family, size);
}
static class SegmentNode extends Group {
final StyledLineRenderer renderer;
final List<Range> ranges = new ArrayList<>();
List<Subscription> boldSubscriptions = new ArrayList<>();
List<Subscription> italicSubscriptions = new ArrayList<>();
List<Subscription> fillSubscriptions = new ArrayList<>();
public SegmentNode(List<String> styleclasses, StyledLineRenderer renderer) {
setManaged(false);
this.renderer = renderer;
getStyleClass().setAll(styleclasses);
}
public Subscription addRange(Range r) {
this.ranges.add(r);
if( bold.getValue() ) {
boldSubscriptions.add( this.renderer.setBold(r) );
}
if( italic.getValue() ) {
italicSubscriptions.add(this.renderer.setItalic(r));
}
fillSubscriptions.add(this.renderer.setForeground(fill.getValue(), r));
return () -> {
if( bold.getValue() ) {
boldSubscriptions.add( this.renderer.setBold(r) );
}
if( italic.getValue() ) {
italicSubscriptions.add(this.renderer.setItalic(r));
}
fillSubscriptions.add(this.renderer.setForeground(fill.getValue(), r));
this.ranges.remove(r);
};
}
private static StyleablePropertyFactory<SegmentNode> FACTORY = new StyleablePropertyFactory<>(Group.getClassCssMetaData());
private static final CssMetaData<SegmentNode, Boolean> BOLD = FACTORY.createBooleanCssMetaData("-efx-styled-bold", s -> s.bold, false, false);
private static final CssMetaData<SegmentNode, Boolean> ITALIC = FACTORY.createBooleanCssMetaData("-efx-styled-italic", s -> s.italic, false, false);
private static final CssMetaData<SegmentNode, Paint> FILL = FACTORY.createPaintCssMetaData("-efx-fill", s -> s.fill, Color.BLACK, false);
private final StyleableProperty<Boolean> bold = new SimpleStyleableBooleanProperty(BOLD, this, "bold") {
protected void invalidated() {
super.invalidated();
SegmentNode.this.renderer.combinedAction( () -> {
SegmentNode.this.boldSubscriptions.forEach(Subscription::dispose);
SegmentNode.this.boldSubscriptions.clear();
if( get() ) {
SegmentNode.this.boldSubscriptions = SegmentNode.this.ranges.stream().map( SegmentNode.this.renderer::setBold).collect(Collectors.toList());
}
});
}
};
private final StyleableProperty<Boolean> italic = new SimpleStyleableBooleanProperty(ITALIC, this, "italic") {
protected void invalidated() {
super.invalidated();
SegmentNode.this.renderer.combinedAction( () -> {
SegmentNode.this.italicSubscriptions.forEach(Subscription::dispose);
SegmentNode.this.italicSubscriptions.clear();
if( get() ) {
SegmentNode.this.italicSubscriptions = SegmentNode.this.ranges.stream().map( SegmentNode.this.renderer::setItalic).collect(Collectors.toList());
}
});
}
};
private final StyleableProperty<Paint> fill = new SimpleStyleableObjectProperty<Paint>(FILL, this, "fill", Color.BLACK) {
protected void invalidated() {
super.invalidated();
SegmentNode.this.renderer.combinedAction( () -> {
Paint p = get();
SegmentNode.this.ranges.forEach(r -> SegmentNode.this.renderer.setForeground(p, r));
});
}
};
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return FACTORY.getCssMetaData();
}
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return FACTORY.getCssMetaData();
}
}
}