Skip to content

Commit c291c7d

Browse files
committed
ParagraphOverlayGraphicFactory improved/fixed:
- now updates layout if editor width changes - separated overlay node creation and layout
1 parent 4308a08 commit c291c7d

2 files changed

Lines changed: 93 additions & 55 deletions

File tree

src/main/java/org/markdownwriterfx/editor/ParagraphOverlayGraphicFactory.java

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.lang.reflect.InvocationTargetException;
3131
import java.lang.reflect.Method;
3232
import java.util.ArrayList;
33+
import java.util.IdentityHashMap;
3334
import java.util.List;
3435
import java.util.function.IntFunction;
3536
import javafx.collections.ObservableList;
@@ -109,6 +110,9 @@ private class ParagraphGraphic
109110
{
110111
private final int paragraphIndex;
111112
private final Node gutter;
113+
private final IdentityHashMap<OverlayFactory, List<Node>> overlayNodesMap
114+
= new IdentityHashMap<>(overlayFactories.size());
115+
private Node paragraphTextNode;
112116

113117
ParagraphGraphic(int paragraphIndex) {
114118
this.paragraphIndex = paragraphIndex;
@@ -128,9 +132,17 @@ private class ParagraphGraphic
128132
} else
129133
gutter = null;
130134

131-
// make this node is the first child so that its nodes are rendered
132-
// 'under' the paragraph text
133135
parentProperty().addListener((observable, oldParent, newParent) -> {
136+
// this node also "need layout" if parent "needs layout"
137+
if (newParent != null) {
138+
newParent.needsLayoutProperty().addListener((ob, o, n) -> {
139+
if (n)
140+
setNeedsLayout(true);
141+
});
142+
}
143+
144+
// make this node is the first child so that its nodes are rendered
145+
// 'under' the paragraph text
134146
if (newParent != null && newParent.getChildrenUnmodifiable().get(0) != this) {
135147
@SuppressWarnings("unchecked")
136148
ObservableList<Node> children = (ObservableList<Node>) invoke(mGetChildren, newParent);
@@ -152,44 +164,42 @@ protected double computePrefHeight(double width) {
152164

153165
@Override
154166
protected void layoutChildren() {
155-
update();
156-
}
157-
158-
private void update() {
159-
getChildren().clear();
167+
// layout gutter
168+
if (gutter != null) {
169+
double gutterWidth = gutter.prefWidth(-1);
170+
layoutInArea(gutter, 0, 0, gutterWidth, getHeight(), -1, null, true, true, HPos.LEFT, VPos.TOP);
171+
}
160172

161-
if (getParent() == null)
162-
return;
173+
// create overlay nodes
174+
if (overlayNodesMap.isEmpty() && !overlayFactories.isEmpty())
175+
createOverlayNodes();
163176

164-
Node paragraphTextNode = getParent().lookup(".paragraph-text");
165-
Insets insets = ((Region)paragraphTextNode).getInsets();
166-
double leftInsets = insets.getLeft();
167-
double topInsets = insets.getTop();
177+
// layout overlay nodes
178+
layoutOverlayNodes();
179+
}
168180

169-
if (gutter != null) {
170-
double prefGutterWidth = gutter.prefWidth(-1);
171-
layoutInArea(gutter, 0, 0, prefGutterWidth, getHeight(), -1, null, true, true, HPos.LEFT, VPos.TOP);
172-
getChildren().add(gutter);
181+
private void createOverlayNodes() {
173182

174-
leftInsets += prefGutterWidth;
175-
}
183+
paragraphTextNode = getParent().lookup(".paragraph-text");
176184

177185
for (OverlayFactory overlayFactory : overlayFactories) {
178-
overlayFactory.init(textArea, paragraphTextNode);
179-
Node[] nodes = overlayFactory.createOverlayNodes(paragraphIndex);
180-
if (nodes == null)
181-
continue;
186+
overlayFactory.init(textArea, paragraphTextNode, gutter);
187+
List<Node> nodes = overlayFactory.createOverlayNodes(paragraphIndex);
188+
overlayNodesMap.put(overlayFactory, nodes);
182189

183-
for (Node node : nodes) {
190+
for (Node node : nodes)
184191
node.setManaged(false);
185-
if (leftInsets != 0)
186-
node.setLayoutX(node.getLayoutX() + leftInsets);
187-
if (topInsets != 0)
188-
node.setLayoutY(node.getLayoutY() + topInsets);
189-
}
192+
190193
getChildren().addAll(nodes);
191194
}
192195
}
196+
197+
private void layoutOverlayNodes() {
198+
for (OverlayFactory overlayFactory : overlayFactories) {
199+
overlayFactory.init(textArea, paragraphTextNode, gutter);
200+
overlayFactory.layoutOverlayNodes(paragraphIndex, overlayNodesMap.get(overlayFactory));
201+
}
202+
}
193203
}
194204

195205
//---- class OverlayFactory -----------------------------------------------
@@ -198,13 +208,18 @@ static abstract class OverlayFactory
198208
{
199209
private StyleClassedTextArea textArea;
200210
private Node paragraphTextNode;
211+
private Node gutter;
212+
private double gutterWidth;
201213

202-
private void init(StyleClassedTextArea textArea, Node paragraphTextNode) {
214+
private void init(StyleClassedTextArea textArea, Node paragraphTextNode, Node gutter) {
203215
this.textArea = textArea;
204216
this.paragraphTextNode = paragraphTextNode;
217+
this.gutter = gutter;
218+
this.gutterWidth = -1;
205219
}
206220

207-
abstract Node[] createOverlayNodes(int paragraphIndex);
221+
abstract List<Node> createOverlayNodes(int paragraphIndex);
222+
abstract void layoutOverlayNodes(int paragraphIndex, List<Node> nodes);
208223

209224
protected StyleClassedTextArea getTextArea() {
210225
return textArea;
@@ -234,6 +249,16 @@ protected Rectangle2D getBounds(int start, int end) {
234249
}
235250
return new Rectangle2D(minX, minY, maxX - minX, maxY - minY);
236251
}
252+
253+
protected Insets getInsets() {
254+
Insets insets = ((Region)paragraphTextNode).getInsets();
255+
if (gutter != null) {
256+
if (gutterWidth < 0)
257+
gutterWidth = gutter.prefWidth(-1);
258+
insets = new Insets(insets.getTop(), insets.getRight(), insets.getBottom(), gutterWidth + insets.getLeft());
259+
}
260+
return insets;
261+
}
237262
}
238263

239264
//---- reflection utilities -----------------------------------------------

src/main/java/org/markdownwriterfx/editor/WhitespaceOverlayFactory.java

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@
2929

3030
import java.util.ArrayList;
3131
import java.util.Collection;
32+
import java.util.List;
33+
import javafx.geometry.Insets;
3234
import javafx.geometry.Rectangle2D;
3335
import javafx.geometry.VPos;
3436
import javafx.scene.Node;
3537
import javafx.scene.text.Text;
3638
import org.fxmisc.richtext.model.Paragraph;
3739
import org.fxmisc.richtext.model.StyledText;
3840
import org.markdownwriterfx.editor.ParagraphOverlayGraphicFactory.OverlayFactory;
39-
import org.reactfx.collection.LiveList;
41+
import org.markdownwriterfx.util.Range;
4042

4143
/**
4244
* Shows whitespace characters.
@@ -46,10 +48,13 @@
4648
class WhitespaceOverlayFactory
4749
extends OverlayFactory
4850
{
51+
private static final String SPACE = "\u00B7";
52+
private static final String TAB = "\u00BB";
53+
private static final String EOL = "\u00B6";
54+
4955
@Override
50-
public Node[] createOverlayNodes(int paragraphIndex) {
51-
LiveList<Paragraph<Collection<String>, Collection<String>>> paragraphs = getTextArea().getParagraphs();
52-
Paragraph<Collection<String>, Collection<String>> par = paragraphs.get(paragraphIndex);
56+
public List<Node> createOverlayNodes(int paragraphIndex) {
57+
Paragraph<Collection<String>, Collection<String>> par = getTextArea().getParagraph(paragraphIndex);
5358

5459
ArrayList<Node> nodes = new ArrayList<>();
5560
int segmentStart = 0;
@@ -61,41 +66,49 @@ public Node[] createOverlayNodes(int paragraphIndex) {
6166
if (ch != ' ' && ch != '\t')
6267
continue;
6368

64-
Rectangle2D bounds = getBounds(segmentStart + i, segmentStart + i + 1);
65-
6669
nodes.add(createTextNode(
67-
(ch == ' ') ? "\u00B7" : "\u00BB",
70+
(ch == ' ') ? SPACE : TAB,
6871
segment.getStyle(),
69-
bounds.getMinX(),
70-
bounds.getMinY()));
72+
segmentStart + i, segmentStart + i + 1));
7173
}
7274

7375
segmentStart += textLength;
7476
}
7577

76-
if (paragraphIndex < paragraphs.size() - 1) {
77-
// all paragraphs except last one have line separators
78-
Rectangle2D bounds = getBounds(segmentStart - 1, segmentStart);
78+
nodes.add(createTextNode(EOL,
79+
par.getStyleAtPosition(segmentStart),
80+
segmentStart - 1, segmentStart));
7981

80-
nodes.add(createTextNode("\u00B6",
81-
par.getStyleAtPosition(segmentStart),
82-
bounds.getMaxX(),
83-
bounds.getMinY()));
84-
}
85-
86-
return nodes.toArray(new Node[nodes.size()]);
82+
return nodes;
8783
}
8884

89-
private Text createTextNode(String text, Collection<String> styleClasses,
90-
double x, double y)
91-
{
85+
private Text createTextNode(String text, Collection<String> styleClasses, int start, int end) {
9286
Text t = new Text(text);
9387
t.setTextOrigin(VPos.TOP);
9488
t.getStyleClass().add("text");
9589
t.setOpacity(0.3);
9690
t.getStyleClass().addAll(styleClasses);
97-
t.setLayoutX(x);
98-
t.setLayoutY(y);
91+
t.setUserData(new Range(start, end));
9992
return t;
10093
}
94+
95+
@Override
96+
void layoutOverlayNodes(int paragraphIndex, List<Node> nodes) {
97+
Insets insets = getInsets();
98+
double leftInsets = insets.getLeft();
99+
double topInsets = insets.getTop();
100+
101+
// all paragraphs except last one have line separators
102+
boolean showEOL = (paragraphIndex < getTextArea().getParagraphs().size() - 1);
103+
Node eolNode = nodes.get(nodes.size() - 1);
104+
if (eolNode.isVisible() != showEOL)
105+
eolNode.setVisible(showEOL);
106+
107+
for (Node node : nodes) {
108+
Range range = (Range) node.getUserData();
109+
Rectangle2D bounds = getBounds(range.start, range.end);
110+
node.setLayoutX(leftInsets + (node == eolNode ? bounds.getMaxX() : bounds.getMinX()));
111+
node.setLayoutY(topInsets + bounds.getMinY());
112+
}
113+
}
101114
}

0 commit comments

Comments
 (0)