-
Notifications
You must be signed in to change notification settings - Fork 54
/
NodeUtils.java
240 lines (225 loc) · 8.51 KB
/
NodeUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*******************************************************************************
* Copyright (c) 2014, 2015 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthias Wienand (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef4.fx.utils;
import java.awt.geom.NoninvertibleTransformException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.gef4.geometry.convert.fx.JavaFX2Geometry;
import org.eclipse.gef4.geometry.planar.AffineTransform;
import org.eclipse.gef4.geometry.planar.IGeometry;
import org.eclipse.gef4.geometry.planar.Point;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
/**
* The {@link NodeUtils} class contains utility methods for working with JavaFX:
* <ul>
* <li>transforming {@link IGeometry}s from/to different JavaFX coordinate
* systems ({@link #localToParent(Node, IGeometry)},
* {@link #localToScene(Node, IGeometry)}, {@link #localToScene(Node, Point)},
* {@link #parentToLocal(Node, IGeometry)},
* {@link #sceneToLocal(Node, IGeometry)})</li>
* <li>determining the actual local-to-scene or scene-to-local transform for a
* JavaFX {@link Node} ({@link #getLocalToSceneTx(Node)},
* {@link #getSceneToLocalTx(Node)})</li>
* <li>perform picking of {@link Node}s at a specific position within the JavaFX
* scene graph ({@link #getNodesAt(Node, double, double)})</li>
* </ul>
*
* @author mwienand
* @author anyssen
*
*/
public class NodeUtils {
/**
* Returns an {@link AffineTransform} which represents the transformation
* matrix to transform geometries from the local coordinate system of the
* given {@link Node} into the coordinate system of the {@link Scene}.
* <p>
* JavaFX {@link Node} provides a (lazily computed) local-to-scene-transform
* property which we could access to get that transform. Unfortunately, this
* property is not updated correctly, i.e. its value can differ from the
* actual local-to-scene-transform. Therefore, we compute the
* local-to-scene-transform for the given node here by concatenating the
* local-to-parent-transforms along the hierarchy.
* <p>
* Note that in situations where you do not need the actual transform, but
* instead perform a transformation, you can use the
* {@link Node#localToScene(Point2D) Node#localToScene(...)} methods on the
* <i>node</i> directly, because it does not make use of the
* local-to-scene-transform property, but uses localToParent() internally.
*
* @param node
* The JavaFX {@link Node} for which the local-to-scene
* transformation matrix is to be computed.
* @return An {@link AffineTransform} representing the local-to-scene
* transformation matrix for the given {@link Node}.
*/
public static AffineTransform getLocalToSceneTx(Node node) {
AffineTransform tx = JavaFX2Geometry
.toAffineTransform(node.getLocalToParentTransform());
Node tmp = node;
while (tmp.getParent() != null) {
tmp = tmp.getParent();
tx = JavaFX2Geometry
.toAffineTransform(tmp.getLocalToParentTransform())
.concatenate(tx);
}
return tx;
}
/**
* Performs picking on the scene graph beginning at the specified root node
* and processing its transitive children.
*
* @param sceneX
* The x-coordinate of the position to pick nodes at, interpreted
* in scene coordinate space.
* @param sceneY
* The y-coordinate of the position to pick nodes at, interpreted
* in scene coordinate space.
* @param root
* The root node at which to start with picking
* @return A list of {@link Node}s which contain the the given coordinate.
*/
public static List<Node> getNodesAt(Node root, double sceneX,
double sceneY) {
List<Node> picked = new ArrayList<Node>();
// start with given root node
List<Node> nodes = new ArrayList<Node>();
nodes.add(root);
while (!nodes.isEmpty()) {
Node current = nodes.remove(0);
// transform to local coordinates
Point2D pLocal = current.sceneToLocal(sceneX, sceneY);
// check if bounds contains (necessary to find children in mouse
// transparent regions)
if (!current.isMouseTransparent()
&& current.getBoundsInLocal().contains(pLocal)) {
// check precisely
if (current.contains(pLocal)) {
picked.add(0, current);
}
// test all children, too
if (current instanceof Parent) {
nodes.addAll(0,
((Parent) current).getChildrenUnmodifiable());
}
}
}
return picked;
}
/**
* Returns the scene-to-local transform for the given {@link Node}.
*
* @param node
* The {@link Node} for which the scene-to-local transform is
* returned.
* @return The scene-to-local transform for the given {@link Node}.
*/
public static AffineTransform getSceneToLocalTx(Node node) {
try {
// IMPORTANT: we make use of getLocalToSceneTx(Node) here to
// compensate that the Transform provided by FX is updated lazily.
// See getLocalToSceneTx(Node) for details.
return getLocalToSceneTx(node).invert();
} catch (NoninvertibleTransformException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Transforms the given {@link IGeometry} from the local coordinate system
* of the given {@link Node} into the coordinate system of the {@link Node}
* 's parent.
*
* @param n
* The {@link Node} used to determine the transformation matrix.
* @param g
* The {@link IGeometry} to transform.
* @return The new, transformed {@link IGeometry}.
*/
public static IGeometry localToParent(Node n, IGeometry g) {
AffineTransform localToParentTx = JavaFX2Geometry
.toAffineTransform(n.getLocalToParentTransform());
return g.getTransformed(localToParentTx);
}
/**
* Transforms the given {@link IGeometry} from the local coordinate system
* of the given {@link Node} into scene coordinates.
*
* @param n
* The {@link Node} used to determine the transformation matrix.
* @param g
* The {@link IGeometry} to transform.
* @return The new, transformed {@link IGeometry}.
*/
public static IGeometry localToScene(Node n, IGeometry g) {
AffineTransform localToSceneTx = getLocalToSceneTx(n);
return g.getTransformed(localToSceneTx);
}
/**
* Transforms the given {@link Point} from the local coordinate system of
* the given {@link Node} into scene coordinates.
*
* @param n
* The {@link Node} used to determine the transformation matrix.
* @param p
* The {@link IGeometry} to transform.
* @return The new, transformed {@link Point}.
*/
public static Point localToScene(Node n, Point p) {
AffineTransform localToSceneTx = getLocalToSceneTx(n);
return localToSceneTx.getTransformed(p);
}
/**
* Transforms the given {@link IGeometry} from the parent coordinate system
* of the given {@link Node} into the local coordinate system of the
* {@link Node}.
*
* @param n
* The {@link Node} used to determine the transformation matrix.
* @param g
* The {@link IGeometry} to transform.
* @return The new, transformed {@link IGeometry}.
*/
public static IGeometry parentToLocal(Node n, IGeometry g) {
// retrieve transform from scene to target parent, by inverting target
// parent to scene
AffineTransform localToParentTx = JavaFX2Geometry
.toAffineTransform(n.getLocalToParentTransform());
AffineTransform parentToLocalTx = null;
try {
parentToLocalTx = localToParentTx.getCopy().invert();
} catch (NoninvertibleTransformException e) {
// TODO: How do we recover from this?!
throw new IllegalStateException(e);
}
return g.getTransformed(parentToLocalTx);
}
/**
* Transforms the given {@link IGeometry} from scene coordinates to the
* local coordinate system of the given {@link Node}.
*
* @param n
* The {@link Node} used to determine the transformation matrix.
* @param g
* The {@link IGeometry} to transform.
* @return The new, transformed {@link IGeometry}.
*/
public static IGeometry sceneToLocal(Node n, IGeometry g) {
// retrieve transform from scene to target parent, by inverting target
// parent to scene
AffineTransform sceneToLocalTx = getSceneToLocalTx(n);
return g.getTransformed(sceneToLocalTx);
}
}