adding experimental code for a new styled text control
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java
new file mode 100644
index 0000000..5e45405
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java
@@ -0,0 +1,155 @@
+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();
+ }
+
+ }
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java
new file mode 100644
index 0000000..f50ed06
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java
@@ -0,0 +1,22 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;
+
+import org.eclipse.fx.core.Range;
+import org.eclipse.fx.core.Subscription;
+
+import javafx.scene.Node;
+import javafx.scene.paint.Paint;
+
+public interface StyledLineRenderer {
+ public void setVisibleRange(double minX, double width);
+
+ public void setFont(String family, double size);
+ public Subscription setBold(Range range);
+ public Subscription setItalic(Range range);
+ public Subscription setForeground(Paint paint, Range range);
+ public void combinedAction(Runnable r);
+ public void setText(char[] text);
+
+ public void clearStyles();
+
+ public Node getNode();
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java
new file mode 100644
index 0000000..b462096
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java
@@ -0,0 +1,5 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;
+
+public interface StyledLineRendererFactory {
+ public StyledLineRenderer createRenderer();
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java
new file mode 100644
index 0000000..72ff010
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java
@@ -0,0 +1,63 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;
+
+import org.eclipse.fx.ui.controls.styledtext.StyledString;
+import org.eclipse.fx.ui.controls.styledtext_ng.internal.SingleCharStyledLineRendererFactory;
+import org.eclipse.fx.ui.controls.styledtext_ng.internal.SingleTextStyledLineRendererFactory;
+
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+public class TestStyledLine extends Application {
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+ VBox p = new VBox();
+ StyledString ss = new StyledString();
+ ss.appendSegment("public", "keyword");
+ ss.appendSegment(" ", "default");
+ ss.appendSegment("class", "keyword");
+ ss.appendSegment(" Hello {", "default");
+
+// {
+// StyledLine l = new StyledLine(new SingleCharStyledLineRendererFactory());
+// l.setFont("monospace", 20);
+// l.setStyledString(ss);
+// p.getChildren().add(l);
+// }
+
+ {
+ StyledLine l = new StyledLine(new SingleTextStyledLineRendererFactory());
+ l.setFont("monospace", 20);
+ l.setStyledString(ss);
+ p.getChildren().add(l);
+ }
+
+
+ Scene s = new Scene(p, 400,400);
+
+ String changeColor = getClass().getResource("color-change.css").toExternalForm();
+
+ ToggleButton b = new ToggleButton("Change Styles");
+ b.setOnAction( e -> {
+ if( s.getStylesheets().contains(changeColor) ) {
+ s.getStylesheets().remove(changeColor);
+ } else {
+ s.getStylesheets().add(changeColor);
+ }
+ });
+ p.getChildren().add(b);
+
+ s.getStylesheets().add(getClass().getResource("test.css").toExternalForm());
+ primaryStage.setScene(s);
+ primaryStage.show();
+ }
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css
new file mode 100644
index 0000000..5a6509c
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css
@@ -0,0 +1,12 @@
+.root {
+ -fx-background-color: black;
+}
+
+.keyword {
+ -efx-styled-bold: true;
+ -efx-fill: #d78b40;
+}
+
+.default {
+ -efx-fill: #b8c4d1;
+}
\ No newline at end of file
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java
new file mode 100644
index 0000000..756e1a0
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java
@@ -0,0 +1,184 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.core.geom.Size;
+import org.eclipse.fx.core.text.TextUtil;
+import org.eclipse.fx.ui.controls.Util;
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+import javafx.scene.paint.Paint;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontPosture;
+import javafx.scene.text.FontWeight;
+
+public abstract class BaseStyledLineRenderer implements StyledLineRenderer {
+ char[] originalText = new char[0];
+ char[] displayedText = new char[0];
+ char[] tabReplace = new char[4];
+
+ Font normal;
+ Font bold;
+ Font italic;
+ Font boldItalic;
+ double w;
+ double h;
+
+ private RangeSet<Integer> boldRange = TreeRangeSet.create();
+ private RangeSet<Integer> italicRange = TreeRangeSet.create();
+ private List<PaintRange> paintRanges = new ArrayList<>();
+
+ int flag = 0;
+ boolean combinedAction;
+
+ static final int FONTS = 1;
+ static final int FILL = 1 << 1;
+ static final int TEXT = 1 << 2;
+
+ public BaseStyledLineRenderer() {
+ Arrays.fill(tabReplace, ' ');
+ }
+
+ protected RangeSet<Integer> getNormalRange() {
+ TreeRangeSet<Integer> set = TreeRangeSet.create();
+ set.add(Range.closed(0, this.originalText.length));
+ set.removeAll(this.boldRange);
+ set.removeAll(this.italicRange);
+ return set;
+ }
+
+ protected RangeSet<Integer> getBoldRange() {
+ if( this.italicRange.isEmpty() ) {
+ return this.boldRange;
+ }
+ RangeSet<Integer> onlyBold = TreeRangeSet.create(this.boldRange);
+ onlyBold.removeAll(this.italicRange);
+ return onlyBold;
+ }
+
+ protected RangeSet<Integer> getItalicRange() {
+ if( this.boldRange.isEmpty() ) {
+ return this.italicRange;
+ }
+ RangeSet<Integer> onlyItalic = TreeRangeSet.create(this.italicRange);
+ onlyItalic.removeAll(this.boldRange);
+ return onlyItalic;
+ }
+
+ protected RangeSet<Integer> getBoldItalicRange() {
+ if( this.italicRange.isEmpty() && this.boldRange.isEmpty() ) {
+ RangeSet<Integer> italicBoldRange = TreeRangeSet.create(boldRange);
+ italicBoldRange.removeAll(italicRange.complement());
+ return italicBoldRange;
+ }
+ return TreeRangeSet.create();
+ }
+
+ protected List<PaintRange> getPaintRanges() {
+ return paintRanges;
+ }
+
+ @Override
+ public void setFont(String family, double size) {
+ this.normal = Font.font(family, size);
+ this.bold = Font.font(family, FontWeight.BOLD, size);
+ this.italic = Font.font(family, FontPosture.ITALIC, size);
+ this.boldItalic = Font.font(family, FontWeight.BOLD, FontPosture.ITALIC, size);
+
+ Size dim = Util.getSize(this.normal, 'A');
+ this.w = dim.width;
+ this.h = dim.height;
+
+ rebuildFonts();
+ }
+
+ @Override
+ public Subscription setBold(org.eclipse.fx.core.Range r) {
+ Range<Integer> range = Range.closed(r.start, r.end-1);
+ this.boldRange.add(range);
+ rebuildFonts();
+ return () -> {
+ this.boldRange.remove(range);
+ rebuildFonts();
+ };
+ }
+
+ @Override
+ public Subscription setItalic(org.eclipse.fx.core.Range r) {
+ Range<Integer> range = Range.closed(r.start, r.end-1);
+ this.italicRange.add(range);
+ rebuildFonts();
+ return () -> {
+ this.italicRange.remove(range);
+ rebuildFonts();
+ };
+ }
+
+ @Override
+ public Subscription setForeground(Paint paint, org.eclipse.fx.core.Range r) {
+ Range<Integer> closed = Range.closed(r.start, r.end-1);
+ for( PaintRange pr : paintRanges ) {
+ if( pr.range.equals(closed) ) {
+ pr.paint = paint;
+ rebuildFill();
+ return null;
+ }
+ }
+
+ PaintRange range = new PaintRange(paint, closed);
+ this.paintRanges.add(range);
+ rebuildFill();
+ return () -> {
+ this.paintRanges.remove(range);
+ rebuildFill();
+ };
+ }
+
+ @Override
+ public void setText(char[] text) {
+ this.originalText = text;
+ this.displayedText = TextUtil.replaceAll(this.originalText, '\t', this.tabReplace);
+ rebuildText();
+ }
+
+ static class PaintRange {
+ Paint paint;
+ final Range<Integer> range;
+
+ public PaintRange(Paint paint, Range<Integer> range) {
+ this.paint = paint;
+ this.range = range;
+ }
+ }
+
+ public void combinedAction(Runnable r) {
+ this.combinedAction = true;
+ try {
+ r.run();
+ } finally {
+ this.combinedAction = false;
+ }
+
+ if( (this.flag & FONTS) == FONTS ) {
+ rebuildFonts();
+ }
+
+ if( (this.flag & FILL) == FILL ) {
+ rebuildFill();
+ }
+ if( (this.flag & TEXT) == TEXT ) {
+ rebuildText();
+ }
+ }
+
+ protected abstract void rebuildFill();
+ protected abstract void rebuildFonts();
+ protected abstract void rebuildText();
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java
new file mode 100644
index 0000000..03aa4bd
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java
@@ -0,0 +1,43 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import javafx.scene.Node;
+
+public class MultiTextStyledLineRenderer extends BaseStyledLineRenderer {
+
+ @Override
+ public void setVisibleRange(double minX, double width) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearStyles() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Node getNode() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ protected void rebuildFill() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ protected void rebuildFonts() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ protected void rebuildText() {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java
new file mode 100644
index 0000000..8454105
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java
@@ -0,0 +1,308 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.core.Triple;
+import org.eclipse.fx.core.geom.Size;
+import org.eclipse.fx.core.text.TextUtil;
+import org.eclipse.fx.ui.controls.Util;
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+import javafx.beans.value.WritableValue;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontPosture;
+import javafx.scene.text.FontWeight;
+import javafx.scene.text.Text;
+
+public class SingleCharStyledLineRenderer implements StyledLineRenderer {
+ private final LayoutPane node;
+ private final int tabAdvance = 4;
+ private int[] tabPositions;
+ private char[] renderedText = new char[0];
+ private char[] originalText = new char[0];
+ double h;
+ double w;
+ List<TextNode> currentNodes = new ArrayList<TextNode>();
+
+ private Font normal;
+ private Font bold;
+ private Font italic;
+ private Font boldItalic;
+
+ private RangeSet<Integer> boldRange = TreeRangeSet.create();
+ private RangeSet<Integer> italicRange = TreeRangeSet.create();
+ private List<PaintRange> paintRanges = new ArrayList<>();
+
+ private boolean combinedAction;
+ private int flag = 0;
+
+ private static final int FONTS = 1;
+ private static final int FILL = 1 << 1;
+
+ public SingleCharStyledLineRenderer() {
+ this.node = new LayoutPane(this);
+ }
+
+ @Override
+ public void setVisibleRange(double minX, double maxX) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setFont(String family, double size) {
+ this.normal = Font.font(family, size);
+ this.bold = Font.font(family, FontWeight.BOLD, size);
+ this.italic = Font.font(family, FontPosture.ITALIC, size);
+ this.boldItalic = Font.font(family, FontWeight.BOLD, FontPosture.ITALIC, size);
+
+ Size dim = Util.getSize(this.normal, 'A');
+ this.w = dim.width;
+ this.h = dim.height;
+ rebuildFonts();
+ this.node.requestLayout();
+ }
+
+ @Override
+ public Subscription setBold(org.eclipse.fx.core.Range r) {
+ Range<Integer> range = Range.closed(r.start, r.end-1);
+ this.boldRange.add(range);
+ rebuildFonts();
+ return () -> {
+ this.boldRange.remove(range);
+ rebuildFonts();
+ };
+ }
+
+ @Override
+ public Subscription setItalic(org.eclipse.fx.core.Range r) {
+ Range<Integer> range = Range.closed(r.start, r.end-1);
+ this.italicRange.add(range);
+ rebuildFonts();
+ return () -> {
+ this.italicRange.remove(range);
+ rebuildFonts();
+ };
+ }
+
+ @Override
+ public Subscription setForeground(Paint paint, org.eclipse.fx.core.Range r) {
+ PaintRange range = new PaintRange(paint, Range.closed(r.start, r.end-1));
+ this.paintRanges.add(range);
+ rebuildFill();
+ return () -> {
+ this.paintRanges.remove(range);
+ rebuildFill();
+ };
+ }
+
+ static class PaintRange {
+ final Paint paint;
+ final Range<Integer> range;
+
+ public PaintRange(Paint paint, Range<Integer> range) {
+ this.paint = paint;
+ this.range = range;
+ }
+ }
+
+ @Override
+ public void setText(char[] text) {
+ Triple<char[], int[], int[]> rv = TextUtil.replaceTabBySpace(text, this.tabAdvance);
+ this.tabPositions = rv.value3;
+ this.originalText = text;
+ if( rv.value3.length == 0 ) {
+ this.renderedText = text;
+ } else {
+ this.renderedText = rv.value1;
+ }
+ rebuild();
+ }
+
+ private Paint getPaint(int index) {
+ for( PaintRange r : this.paintRanges ) {
+ if( r.range.contains(index) ) {
+ return r.paint;
+ }
+ }
+
+ return Color.BLACK;
+ }
+
+ private void rebuild() {
+ List<TextNode> l = new ArrayList<>(this.originalText.length);
+ for( int i = 0; i < this.renderedText.length; i++ ) {
+ if( this.renderedText[i] != ' ' ) {
+ TextNode node = new TextNode(i,this.renderedText[i]);
+ node.setFontData(this.boldRange.contains(i), this.italicRange.contains(i));
+ node.setFill(getPaint(i));
+ updateFont(node);
+ l.add( node );
+
+ }
+ }
+ this.currentNodes = l;
+ this.node.getChildren().setAll(this.currentNodes);
+
+ this.node.requestLayout();
+ }
+
+ private void rebuildFonts() {
+ if( this.combinedAction ) {
+ this.flag |= FONTS;
+ } else {
+ for( TextNode t : this.currentNodes ) {
+ t.setFontData(this.boldRange.contains(t.index), this.italicRange.contains(t.index));
+ }
+ this.currentNodes.forEach(this::updateFont);
+ }
+ }
+
+ private void rebuildFill() {
+ if( this.combinedAction ) {
+ this.flag |= FILL;
+ } else {
+ this.currentNodes.forEach( n -> n.setFill(getPaint(n.index)));
+ }
+ }
+
+ public void combinedAction(Runnable r) {
+ this.combinedAction = true;
+ try {
+ r.run();
+ } finally {
+ this.combinedAction = false;
+ }
+
+ if( (this.flag & FONTS) == FONTS ) {
+ rebuildFonts();
+ }
+
+ if( (this.flag & FILL) == FILL ) {
+ rebuildFill();
+ }
+ }
+
+ private void updateFont(TextNode node) {
+ if( node.bold && node.italic ) {
+ node.setFont(this.boldItalic);
+ } else if( node.bold ) {
+ node.setFont(this.bold);
+ } else if( node.italic ) {
+ node.setFont(this.italic);
+ } else {
+ node.setFont(this.normal);
+ }
+ }
+
+ @Override
+ public void clearStyles() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Node getNode() {
+ return this.node;
+ }
+
+ public double getCharWidth() {
+ return this.w;
+ }
+
+ public double getCharHeight() {
+ return this.h;
+ }
+
+ public double getWidth() {
+ return this.w * this.renderedText.length;
+ }
+
+ public double getHeight() {
+ return this.h;
+ }
+
+ static class TextNode extends Text {
+ final int index;
+ boolean bold;
+ boolean italic;
+
+ public TextNode(int index, char c) {
+ super(TextUtil.toString(c));
+ setManaged(false);
+ this.index = index;
+ }
+
+ public void setFontData(boolean bold, boolean italic) {
+ this.bold = bold;
+ this.italic = italic;
+ }
+ }
+
+ static class LayoutPane extends Region {
+ private SingleCharStyledLineRenderer r;
+ public LayoutPane(SingleCharStyledLineRenderer r) {
+ this.r = r;
+ }
+
+ @Override
+ public ObservableList<Node> getChildren() {
+ return super.getChildren();
+ }
+
+// @Override
+// protected void impl_processCSS(WritableValue<Boolean> unused) {
+// // no css in this area
+// }
+
+ @Override
+ protected void layoutChildren() {
+ double w = this.r.getCharWidth();
+ double h = this.r.getCharHeight();
+ this.r.currentNodes.forEach( n -> n.resizeRelocate(n.index * w, 0, w, h));
+ }
+
+ @Override
+ protected double computeMinWidth(double height) {
+ return r.getWidth();
+ }
+
+ @Override
+ protected double computeMinHeight(double width) {
+ System.err.println("MIn: " + getHeight());
+ return r.getCharHeight();
+ }
+
+ @Override
+ protected double computePrefHeight(double width) {
+ System.err.println("PREF: " + getHeight());
+ return r.getCharHeight();
+ }
+
+ @Override
+ protected double computePrefWidth(double height) {
+ return r.getWidth();
+ }
+
+ @Override
+ protected double computeMaxHeight(double width) {
+ return r.getCharHeight();
+ }
+
+ @Override
+ protected double computeMaxWidth(double height) {
+ return r.getWidth();
+ }
+ }
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java
new file mode 100644
index 0000000..142b101
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java
@@ -0,0 +1,13 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRendererFactory;
+
+public class SingleCharStyledLineRendererFactory implements StyledLineRendererFactory {
+
+ @Override
+ public StyledLineRenderer createRenderer() {
+ return new SingleCharStyledLineRenderer();
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java
new file mode 100644
index 0000000..001216a
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java
@@ -0,0 +1,173 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.fx.core.text.TextUtil;
+import org.eclipse.fx.ui.controls.Util;
+
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Paint;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+
+public class SingleTextStyledLineRenderer extends BaseStyledLineRenderer {
+ private final LayoutPane node;
+
+ public SingleTextStyledLineRenderer() {
+ this.node = new LayoutPane();
+ }
+
+ @Override
+ public void setVisibleRange(double minX, double width) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearStyles() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
+
+
+ @Override
+ protected void rebuildFill() {
+ rebuildText();
+ }
+
+ @Override
+ protected void rebuildFonts() {
+ rebuildText();
+ }
+
+ @Override
+ protected void rebuildText() {
+ if( this.combinedAction ) {
+ this.flag |= TEXT;
+ return;
+ }
+
+ this.node.requestLayout();
+ }
+
+ private void refreshLayout() {
+ if( this.flag == 0 ) {
+ return;
+ }
+ this.flag = 0;
+ RangeSet<Integer> normalFont = getNormalRange();
+
+ RangeSet<Integer> boldRange = getBoldRange();
+ RangeSet<Integer> italicRange = getItalicRange();
+ RangeSet<Integer> boldItalicRange = getBoldItalicRange();
+
+ Map<Paint, RangeSet<Integer>> normalTextColors = new HashMap<>();
+ Map<Paint, RangeSet<Integer>> boldTextColors = new HashMap<>();
+ Map<Paint, RangeSet<Integer>> italicTextColors = new HashMap<>();
+ Map<Paint, RangeSet<Integer>> italicBoldTextColors = new HashMap<>();
+
+ for( PaintRange r : getPaintRanges() ) {
+ if( ! normalFont.subRangeSet(r.range).isEmpty() ) {
+ RangeSet<Integer> set = normalTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());
+ set.addAll(normalFont.subRangeSet(r.range));
+ } else {
+ if( ! boldItalicRange.isEmpty() && ! boldItalicRange.subRangeSet(r.range).isEmpty() ) {
+ RangeSet<Integer> set = italicBoldTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());
+ set.addAll(boldItalicRange.subRangeSet(r.range));
+ } else if( ! italicRange.subRangeSet(r.range).isEmpty() ) {
+ RangeSet<Integer> set = italicTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());
+ set.addAll(italicRange.subRangeSet(r.range));
+ } else if( ! boldRange.subRangeSet(r.range).isEmpty() ) {
+ RangeSet<Integer> set = boldTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());
+ set.addAll(boldRange.subRangeSet(r.range));
+ }
+ }
+ }
+
+ Arrays.fill(this.tabReplace, ' ');
+
+ List<Node> l = new ArrayList<>();
+ l.addAll(createTextNodes(normalTextColors, this.originalText, this.tabReplace, this.normal));
+ l.addAll(createTextNodes(boldTextColors, this.originalText, this.tabReplace, this.bold));
+ l.addAll(createTextNodes(italicTextColors, this.originalText, this.tabReplace, this.italic));
+ l.addAll(createTextNodes(italicBoldTextColors, this.originalText, this.tabReplace, this.boldItalic));
+
+ l.stream().forEach( n -> System.err.println(n));
+
+ this.node.getChildren().setAll(l);
+ }
+
+ private static List<Text> createTextNodes(Map<Paint, RangeSet<Integer>> textColorRanges, char[] text, char[] tabReplace, Font font) {
+ List<Text> nodes = new ArrayList<>();
+
+ for( Entry<Paint, RangeSet<Integer>> e : textColorRanges.entrySet() ) {
+ char[] txt = TextUtil.replace(text, ' ', ( idx, ch ) -> {
+ return ch != '\t' && ! e.getValue().contains(idx);
+ });
+ Text tNode = new Text(String.valueOf(TextUtil.replaceAll(txt, '\r', tabReplace)));
+ tNode.setFont(font);
+ tNode.setFill(e.getKey());
+ nodes.add(tNode);
+ }
+
+ return nodes;
+ }
+
+ class LayoutPane extends Region {
+ @Override
+ protected ObservableList<Node> getChildren() {
+ return super.getChildren();
+ }
+
+ @Override
+ protected void layoutChildren() {
+ refreshLayout();
+ getChildren().forEach( c -> c.resizeRelocate(0, 0, c.prefWidth(-1), c.prefHeight(-1)));
+ }
+
+ @Override
+ protected double computeMinHeight(double width) {
+ return Util.getSize(normal, ' ').height;
+ }
+
+ @Override
+ protected double computePrefHeight(double width) {
+ return super.computeMinHeight(width);
+ }
+
+ @Override
+ protected double computeMaxHeight(double width) {
+ return super.computeMinHeight(width);
+ }
+
+ @Override
+ protected double computeMinWidth(double height) {
+ return Util.getSize(SingleTextStyledLineRenderer.this.normal, ' ').width * SingleTextStyledLineRenderer.this.displayedText.length;
+ }
+
+ @Override
+ protected double computePrefWidth(double height) {
+ return super.computeMinWidth(height);
+ }
+
+ @Override
+ protected double computeMaxWidth(double height) {
+ return super.computeMinWidth(height);
+ }
+ }
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java
new file mode 100644
index 0000000..430f44b
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java
@@ -0,0 +1,13 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;
+
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;
+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRendererFactory;
+
+public class SingleTextStyledLineRendererFactory implements StyledLineRendererFactory {
+
+ @Override
+ public StyledLineRenderer createRenderer() {
+ return new SingleTextStyledLineRenderer();
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css
new file mode 100644
index 0000000..021aa37
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css
@@ -0,0 +1,8 @@
+.keyword {
+ -efx-styled-bold: true;
+ -efx-fill: rgb(127, 0, 85);
+}
+
+.default {
+ -efx-fill: black;
+}
\ No newline at end of file