1 package org.simantics.district.network.ui.nodes;
5 import java.awt.FontMetrics;
6 import java.awt.Graphics2D;
7 import java.awt.geom.AffineTransform;
8 import java.awt.geom.Point2D;
9 import java.awt.geom.Rectangle2D;
10 import java.util.Arrays;
11 import java.util.List;
12 import java.util.Objects;
13 import java.util.concurrent.atomic.AtomicReference;
14 import java.util.function.ToIntFunction;
16 import org.simantics.scenegraph.INode;
17 import org.simantics.scenegraph.NodeException;
18 import org.simantics.scenegraph.ParentNode;
19 import org.simantics.scenegraph.g2d.G2DNode;
20 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
21 import org.simantics.scenegraph.profile.common.ProfileVariables;
22 import org.simantics.scenegraph.utils.DPIUtil;
23 import org.simantics.scenegraph.utils.GeometryUtils;
24 import org.simantics.scenegraph.utils.NodeUtil;
25 import org.simantics.scl.runtime.Lists;
26 import org.simantics.scl.runtime.tuple.Tuple3;
28 public class DistrictNetworkHoverInfoNode extends G2DNode implements HoverSensitiveNode, DeferredNode {
30 private static final long serialVersionUID = 1L;
32 private static final String HOVER_INFO_DEFERRED = "hoverInfo";
34 public static final String NODE_KEY = "DISTRICT_NETWORK_HOVER_INFO";
36 private static final int PAD = 15;
38 private List<Tuple3> labels;
40 private Font font = new Font(Font.SANS_SERIF, Font.PLAIN, DPIUtil.upscale(14));
42 @SuppressWarnings("unused")
43 private Point2D origin;
45 private boolean hover = false;
47 private Point2D mousePosition;
50 * For rendering and clipping the hover info base rectangle.
52 private Rectangle2D bgRect = new Rectangle2D.Double();
54 private static AtomicReference<INode> activeNode = new AtomicReference<>();
57 public void render(Graphics2D g) {
58 ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(this, RTreeNode.class);
59 DeferredRenderingNode deferred = root != null ? (DeferredRenderingNode) root.getNode(HOVER_INFO_DEFERRED) : null;
61 deferred.deferNode(g.getTransform(), this);
67 public void renderDeferred(Graphics2D g) {
68 if (!hover || activeNode.get() == null)
70 if (labels == null || labels.isEmpty() || mousePosition == null)
72 AffineTransform ot = g.getTransform();
73 Font of = g.getFont();
79 private ToIntFunction<String> widthMeasurer(FontMetrics fm) {
80 return s -> fm.stringWidth(s);
83 private void doRender(Graphics2D g) {
84 AffineTransform tt = getTransform();
87 g.translate(mousePosition.getX(), mousePosition.getY());
88 //g.translate(origin.getX(), origin.getY());
89 double scale = 1.0 / GeometryUtils.getScale(g.getTransform());
90 g.scale(scale, scale);
93 FontMetrics fm = g.getFontMetrics();
94 ToIntFunction<String> sizer = widthMeasurer(fm);
96 double rowHeight = fm.getHeight() * 1.1;
98 // Let's calculate the max widths of each column.
99 // However, the last label is assumed to be a title row.
100 List<Tuple3> values = labels.subList(0, labels.size() - 1);
101 Tuple3 title = labels.get(labels.size() - 1);
103 int maxLabelWidth = values.stream().map(t -> (String) t.c0).mapToInt(sizer).max().orElse(PAD);
104 int[] valueWidths = values.stream().map(t -> (String) t.c1).mapToInt(sizer).toArray();
105 int maxValueWidth = Arrays.stream(valueWidths).max().orElse(PAD);
106 int maxUnitWidth = values.stream().map(t -> (String) t.c2).mapToInt(sizer).max().orElse(PAD);
109 (Objects.toString(title.c0, "")
110 + " " + Objects.toString(title.c1, "")
111 + " " + Objects.toString(title.c2, ""))
113 int titleWidth = sizer.applyAsInt(titleString);
115 int titleRowWidth = PAD + titleWidth + PAD;
116 int maxValueRowWidth = PAD + maxLabelWidth + PAD + maxValueWidth + PAD + maxUnitWidth + PAD;
118 int totalWidth = Math.max(maxValueRowWidth, titleRowWidth);
119 int totalHeight = (int) Math.round((rowHeight) * labels.size());
121 double minX = -(PAD + maxLabelWidth + PAD + maxValueWidth + PAD);
122 double minY = -(totalHeight + (int)Math.round(rowHeight));
124 bgRect.setRect(minX, minY, totalWidth, totalHeight + PAD);
125 confineIn(bgRect, g.getClip().getBounds2D());
127 g.setColor(Color.WHITE);
130 g.setColor(Color.DARK_GRAY);
133 g.setColor(Color.BLACK);
134 g.translate(bgRect.getMinX() - minX, bgRect.getMinY() - minY);
136 int rows = values.size();
137 double y = -rowHeight;
139 float labelX = -(maxLabelWidth + PAD + maxValueWidth + PAD);
142 for (int i = 0; i < rows; ++i) {
143 Tuple3 t = values.get(i);
144 float ty = (float) y;
146 if (t.c0 != null && ((String) t.c0).length() > 0) {
147 g.drawString((String) t.c0, labelX, ty);
149 if (t.c1 != null && ((String) t.c1).length() > 0) {
150 g.drawString((String) t.c1, -(valueWidths[i] + PAD), ty);
152 if (t.c2 != null && ((String) t.c2).length() > 0) {
153 g.drawString((String) t.c2, unitX, ty);
159 if (!titleString.trim().isEmpty()) {
160 int titleX = (int) (labelX - PAD + (bgRect.getWidth() - titleWidth) * 0.5);
161 g.drawString(titleString, titleX, (float) y);
165 private Rectangle2D confineIn(Rectangle2D r, Rectangle2D bounds) {
166 // Handle right/bottom overrun
167 double maxX = Math.min(r.getMaxX(), bounds.getMaxX());
168 double maxY = Math.min(r.getMaxY(), bounds.getMaxY());
171 maxY - r.getHeight(),
175 // Handle left/top overrun
177 Math.max(r.getMinX(), bounds.getMinX()),
178 Math.max(r.getMinY(), bounds.getMinY()),
186 public Rectangle2D getBoundsInLocal() {
190 @SuppressWarnings("unchecked")
191 public void setLabels(List<Tuple3> list) {
192 this.labels = Lists.reverse(list);
195 public void setOrigin(Point2D origin) {
196 this.origin = origin;
200 public boolean hover(boolean hover, boolean isConnectionTool) {
201 boolean changed = hover != this.hover;
207 public void setMousePosition(Point2D p) {
212 public void delete() {
214 activeNode.set(null);
217 public void setHoveredNode(INode hoveredNode) {
218 ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(parent, RTreeNode.class);
221 INode child = ProfileVariables.browseChild(root, "");
223 throw new NullPointerException("Scenegraph child node was not found: " + "");
226 INode existing = NodeUtil.getChildById(child, HOVER_INFO_DEFERRED);
227 if (existing == null) {
228 if (child instanceof ParentNode<?>) {
229 existing = ((ParentNode<?>) child).addNode(HOVER_INFO_DEFERRED, DeferredRenderingNode.class);
230 ((DeferredRenderingNode)existing).setZIndex(Integer.MAX_VALUE);
232 throw new NodeException("Cannot claim child node for non-parent-node " + child);
237 activeNode.set(hoveredNode);