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.district.network.ui.styles.DistrictNetworkHoverInfoStyle;
17 import org.simantics.maps.MapScalingTransform;
18 import org.simantics.scenegraph.INode;
19 import org.simantics.scenegraph.NodeException;
20 import org.simantics.scenegraph.ParentNode;
21 import org.simantics.scenegraph.g2d.G2DNode;
22 import org.simantics.scenegraph.g2d.G2DParentNode;
23 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
24 import org.simantics.scenegraph.profile.common.ProfileVariables;
25 import org.simantics.scenegraph.utils.DPIUtil;
26 import org.simantics.scenegraph.utils.GeometryUtils;
27 import org.simantics.scenegraph.utils.NodeUtil;
28 import org.simantics.scl.runtime.Lists;
29 import org.simantics.scl.runtime.tuple.Tuple3;
31 public class DistrictNetworkHoverInfoNode extends G2DNode implements HoverSensitiveNode, DeferredNode {
33 private static final long serialVersionUID = 1L;
35 public static final String NODE_KEY = "DISTRICT_NETWORK_HOVER_INFO";
37 private static final int PAD = 15;
39 private List<Tuple3> labels;
41 private Font font = new Font(
44 (int)(DPIUtil.upscale(9) * MapScalingTransform.getScaleY() + 0.5));
46 @SuppressWarnings("unused")
47 private Point2D origin;
49 private boolean hover = false;
51 private Point2D mousePosition;
54 * For rendering and clipping the hover info base rectangle.
56 private Rectangle2D bgRect = new Rectangle2D.Double();
58 private static AtomicReference<G2DParentNode> activeNode = new AtomicReference<>();
61 public void render(Graphics2D g) {
62 ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(this, RTreeNode.class);
63 DeferredRenderingNode deferred = root != null ? (DeferredRenderingNode) root.getNode(DistrictNetworkHoverInfoStyle.HOVER_INFO_DEFERRED) : null;
65 deferred.deferNode(g.getTransform(), this);
71 public void renderDeferred(Graphics2D g) {
72 if (!hover || activeNode.get() == null)
74 if (labels.isEmpty() || mousePosition == null)
76 AffineTransform ot = g.getTransform();
77 Font of = g.getFont();
83 private ToIntFunction<String> widthMeasurer(FontMetrics fm) {
84 return s -> fm.stringWidth(s);
87 private void doRender(Graphics2D g) {
88 AffineTransform tt = getTransform();
91 g.translate(mousePosition.getX(), mousePosition.getY());
92 //g.translate(origin.getX(), origin.getY());
93 double scale = DPIUtil.upscale( 1.25 / GeometryUtils.getScale(g.getTransform()) );
94 g.scale(scale, scale);
98 FontMetrics fm = g.getFontMetrics();
99 ToIntFunction<String> sizer = widthMeasurer(fm);
101 double rowHeight = fm.getHeight() * 1.1;
103 // Let's calculate the max widths of each column.
104 // However, the last label is assumed to be a title row.
105 List<Tuple3> values = labels.subList(0, labels.size() - 1);
106 Tuple3 title = labels.get(labels.size() - 1);
108 int maxLabelWidth = values.stream().map(t -> (String) t.c0).mapToInt(sizer).max().orElse(PAD);
109 int[] valueWidths = values.stream().map(t -> (String) t.c1).mapToInt(sizer).toArray();
110 int maxValueWidth = Arrays.stream(valueWidths).max().orElse(PAD);
111 int maxUnitWidth = values.stream().map(t -> (String) t.c2).mapToInt(sizer).max().orElse(PAD);
114 (Objects.toString(title.c0, "")
115 + " " + Objects.toString(title.c1, "")
116 + " " + Objects.toString(title.c2, ""))
118 int titleWidth = sizer.applyAsInt(titleString);
120 int titleRowWidth = PAD + titleWidth + PAD;
121 int maxValueRowWidth = PAD + maxLabelWidth + PAD + maxValueWidth + PAD + maxUnitWidth + PAD;
123 int totalWidth = Math.max(maxValueRowWidth, titleRowWidth);
124 int totalHeight = (int) Math.round((rowHeight) * labels.size());
126 double minX = -(PAD + maxLabelWidth + PAD + maxValueWidth + PAD);
127 double minY = -(totalHeight + (int)Math.round(rowHeight));
129 bgRect.setRect(minX, minY, totalWidth, totalHeight + PAD);
130 confineIn(bgRect, g.getClip().getBounds2D());
132 g.setColor(Color.WHITE);
135 g.setColor(Color.DARK_GRAY);
138 g.setColor(Color.BLACK);
139 g.translate(bgRect.getMinX() - minX, bgRect.getMinY() - minY);
141 int rows = values.size();
142 double y = -rowHeight;
144 float labelX = -(maxLabelWidth + PAD + maxValueWidth + PAD);
147 for (int i = 0; i < rows; ++i) {
148 Tuple3 t = values.get(i);
149 float ty = (float) y;
151 if (t.c0 != null && ((String) t.c0).length() > 0) {
152 g.drawString((String) t.c0, labelX, ty);
154 if (t.c1 != null && ((String) t.c1).length() > 0) {
155 g.drawString((String) t.c1, -(valueWidths[i] + PAD), ty);
157 if (t.c2 != null && ((String) t.c2).length() > 0) {
158 g.drawString((String) t.c2, unitX, ty);
164 if (!titleString.trim().isEmpty()) {
165 int titleX = (int) (labelX - PAD + (bgRect.getWidth() - titleWidth) * 0.5);
166 g.drawString(titleString, titleX, (float) y);
170 private Rectangle2D confineIn(Rectangle2D r, Rectangle2D bounds) {
171 // Handle right/bottom overrun
172 double maxX = Math.min(r.getMaxX(), bounds.getMaxX());
173 double maxY = Math.min(r.getMaxY(), bounds.getMaxY());
176 maxY - r.getHeight(),
180 // Handle left/top overrun
182 Math.max(r.getMinX(), bounds.getMinX()),
183 Math.max(r.getMinY(), bounds.getMinY()),
191 public Rectangle2D getBoundsInLocal() {
195 @SuppressWarnings("unchecked")
196 public void setLabels(List<Tuple3> list) {
197 this.labels = Lists.reverse(list);
200 public void setOrigin(Point2D origin) {
201 this.origin = origin;
205 public boolean hover(boolean hover, boolean isConnectionTool) {
206 // hover = hover && activeNode.updateAndGet(current -> current == null ? this : current) == this;
207 boolean changed = hover != this.hover;
211 // if (!hover) activeNode.updateAndGet(current -> current == this ? null : current);
219 public void setMousePosition(Point2D p) {
224 public void delete() {
226 activeNode.set(null);
229 public void hover2(G2DParentNode hoveredNode) {
230 ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(parent, RTreeNode.class);
233 INode child = ProfileVariables.browseChild(root, "");
235 throw new NullPointerException("Scenegraph child node was not found: " + "");
238 INode existing = NodeUtil.getChildById(child, DistrictNetworkHoverInfoStyle.HOVER_INFO_DEFERRED);
239 if (existing == null) {
240 if (child instanceof ParentNode<?>) {
241 existing = ((ParentNode<?>) child).addNode(DistrictNetworkHoverInfoStyle.HOVER_INFO_DEFERRED, DeferredRenderingNode.class);
242 ((DeferredRenderingNode)existing).setZIndex(Integer.MAX_VALUE);
244 throw new NodeException("Cannot claim child node for non-parent-node " + child);
249 activeNode.set(hoveredNode);