import java.awt.Color;
import java.awt.Font;
+import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
-import java.util.Comparator;
+import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.ToIntFunction;
import org.simantics.district.network.ui.styles.DistrictNetworkHoverInfoStyle;
import org.simantics.maps.MapScalingTransform;
import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
import org.simantics.scenegraph.profile.common.ProfileVariables;
import org.simantics.scenegraph.utils.DPIUtil;
+import org.simantics.scenegraph.utils.GeometryUtils;
import org.simantics.scenegraph.utils.NodeUtil;
import org.simantics.scl.runtime.Lists;
import org.simantics.scl.runtime.tuple.Tuple3;
public class DistrictNetworkHoverInfoNode extends G2DNode implements HoverSensitiveNode, DeferredNode {
- private static final Font FONT = new Font("Tahoma", Font.PLAIN, (int)(DPIUtil.upscale(9) * MapScalingTransform.getScaleY() + 0.5));
-
- private static final long serialVersionUID = 1L;
-
- public static final String NODE_KEY = "DISTRICT_NETWORK_HOVER_INFO";
-
- private static final int W1 = 50;
- private static final int W2 = 30;
- private static final int PAD = 5;
-
- private List<Tuple3> labels;
-
- @SuppressWarnings("unused")
- private Point2D origin;
-
- private boolean hover = false;
-
- private Point2D mousePosition;
-
- private static AtomicReference<G2DParentNode> activeNode = new AtomicReference<>();
-
- @Override
- public void render(Graphics2D g) {
-
- ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(this, RTreeNode.class);
- DeferredRenderingNode deferred = root != null ? (DeferredRenderingNode) root.getNode(DistrictNetworkHoverInfoStyle.HOVER_INFO_DEFERRED) : null;
- if (deferred != null)
- deferred.deferNode(g.getTransform(), this);
- else
- renderDeferred(g);
- }
-
- @Override
- public void renderDeferred(Graphics2D g) {
- if (!hover || activeNode.get() == null)
- return;
- AffineTransform ot = g.getTransform();
- Font of = g.getFont();
- doRender(g);
- g.setFont(of);
- g.setTransform(ot);
- }
-
- private void doRender(Graphics2D g) {
- AffineTransform tt = getTransform();
- g.transform(tt);
- if (mousePosition == null)
- return;
- g.translate(mousePosition.getX(), mousePosition.getY());
- //g.translate(origin.getX(), origin.getY());
- double scale = 1.5 / g.getTransform().getScaleX();
- g.scale(scale, scale);
-
- g.setFont(FONT);
- double rowHeight = g.getFontMetrics().getHeight() * 1.1;
-
- // let's calculate the max width
- Optional<Integer> max = labels.stream().map(t -> g.getFontMetrics().stringWidth((String) t.c2)).max(Comparator.naturalOrder());
- int width = max.orElse(10);
+ private static final long serialVersionUID = 1L;
+
+ public static final String NODE_KEY = "DISTRICT_NETWORK_HOVER_INFO";
+
+ private static final int PAD = 15;
+
+ private List<Tuple3> labels;
+
+ private Font font = new Font(
+ Font.SANS_SERIF,
+ Font.PLAIN,
+ (int)(DPIUtil.upscale(9) * MapScalingTransform.getScaleY() + 0.5));
+
+ @SuppressWarnings("unused")
+ private Point2D origin;
+
+ private boolean hover = false;
+
+ private Point2D mousePosition;
+
+ /**
+ * For rendering and clipping the hover info base rectangle.
+ */
+ private Rectangle2D bgRect = new Rectangle2D.Double();
+
+ private static AtomicReference<G2DParentNode> activeNode = new AtomicReference<>();
+
+ @Override
+ public void render(Graphics2D g) {
+ ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(this, RTreeNode.class);
+ DeferredRenderingNode deferred = root != null ? (DeferredRenderingNode) root.getNode(DistrictNetworkHoverInfoStyle.HOVER_INFO_DEFERRED) : null;
+ if (deferred != null)
+ deferred.deferNode(g.getTransform(), this);
+ else
+ renderDeferred(g);
+ }
+
+ @Override
+ public void renderDeferred(Graphics2D g) {
+ if (!hover || activeNode.get() == null)
+ return;
+ if (labels.isEmpty() || mousePosition == null)
+ return;
+ AffineTransform ot = g.getTransform();
+ Font of = g.getFont();
+ doRender(g);
+ g.setFont(of);
+ g.setTransform(ot);
+ }
+
+ private ToIntFunction<String> widthMeasurer(FontMetrics fm) {
+ return s -> fm.stringWidth(s);
+ }
+
+ private void doRender(Graphics2D g) {
+ AffineTransform tt = getTransform();
+ g.transform(tt);
+
+ g.translate(mousePosition.getX(), mousePosition.getY());
+ //g.translate(origin.getX(), origin.getY());
+ double scale = DPIUtil.upscale( 1.25 / GeometryUtils.getScale(g.getTransform()) );
+ g.scale(scale, scale);
+
+ g.setFont(font);
+
+ FontMetrics fm = g.getFontMetrics();
+ ToIntFunction<String> sizer = widthMeasurer(fm);
+
+ double rowHeight = fm.getHeight() * 1.1;
+
+ // Let's calculate the max widths of each column.
+ // However, the last label is assumed to be a title row.
+ List<Tuple3> values = labels.subList(0, labels.size() - 1);
+ Tuple3 title = labels.get(labels.size() - 1);
+
+ int maxLabelWidth = values.stream().map(t -> (String) t.c0).mapToInt(sizer).max().orElse(PAD);
+ int[] valueWidths = values.stream().map(t -> (String) t.c1).mapToInt(sizer).toArray();
+ int maxValueWidth = Arrays.stream(valueWidths).max().orElse(PAD);
+ int maxUnitWidth = values.stream().map(t -> (String) t.c2).mapToInt(sizer).max().orElse(PAD);
+
+ String titleString =
+ (Objects.toString(title.c0, "")
+ + " " + Objects.toString(title.c1, "")
+ + " " + Objects.toString(title.c2, ""))
+ .trim();
+ int titleWidth = sizer.applyAsInt(titleString);
+
+ int titleRowWidth = PAD + titleWidth + PAD;
+ int maxValueRowWidth = PAD + maxLabelWidth + PAD + maxValueWidth + PAD + maxUnitWidth + PAD;
+
+ int totalWidth = Math.max(maxValueRowWidth, titleRowWidth);
+ int totalHeight = (int) Math.round((rowHeight) * labels.size());
+
+ double minX = -(PAD + maxLabelWidth + PAD + maxValueWidth + PAD);
+ double minY = -(totalHeight + (int)Math.round(rowHeight));
+
+ bgRect.setRect(minX, minY, totalWidth, totalHeight + PAD);
+ confineIn(bgRect, g.getClip().getBounds2D());
+
g.setColor(Color.WHITE);
- int totalHeight = (int)Math.round(rowHeight * labels.size());
- g.fillRect(-(W1 + PAD + W2 + 5), -(totalHeight + (int)Math.round(rowHeight)), (W1 + PAD + W2 + width + 10), totalHeight + 5);
-
- g.setColor(Color.BLACK);
-
- for (Tuple3 t : labels) {
- g.translate(0.f, -rowHeight);
-
- if (t.c0 != null) {
- g.drawString((String) t.c0, -(W1 + PAD + W2), 0.f);
- }
-
- if (t.c1 != null) {
- int width1 = g.getFontMetrics().stringWidth((String) t.c1);
- g.drawString((String) t.c1, - width1, 0.f);
- }
-
- if (t.c2 != null) {
- g.drawString((String) t.c2, PAD, 0.f);
- }
- }
- }
-
- @Override
- public Rectangle2D getBoundsInLocal() {
- return null;
- }
-
- @SuppressWarnings("unchecked")
- public void setLabels(List<Tuple3> list) {
- this.labels = Lists.reverse(list);
- }
-
- public void setOrigin(Point2D origin) {
- this.origin = origin;
- }
-
- @Override
- public boolean hover(boolean hover, boolean isConnectionTool) {
-// hover = hover && activeNode.updateAndGet(current -> current == null ? this : current) == this;
- boolean changed = hover != this.hover;
- this.hover = hover;
-//
-// if (changed) {
-// if (!hover) activeNode.updateAndGet(current -> current == this ? null : current);
-// repaint();
-// }
-//
- return changed;
- }
-
- @Override
- public void setMousePosition(Point2D p) {
- mousePosition = p;
- }
-
- @Override
- public void delete() {
- super.delete();
- activeNode.set(null);
- }
+ g.fill(bgRect);
+
+ g.setColor(Color.DARK_GRAY);
+ g.draw(bgRect);
+
+ g.setColor(Color.BLACK);
+ g.translate(bgRect.getMinX() - minX, bgRect.getMinY() - minY);
+
+ int rows = values.size();
+ double y = -rowHeight;
+
+ float labelX = -(maxLabelWidth + PAD + maxValueWidth + PAD);
+ float unitX = 0;
+
+ for (int i = 0; i < rows; ++i) {
+ Tuple3 t = values.get(i);
+ float ty = (float) y;
+
+ if (t.c0 != null && ((String) t.c0).length() > 0) {
+ g.drawString((String) t.c0, labelX, ty);
+ }
+ if (t.c1 != null && ((String) t.c1).length() > 0) {
+ g.drawString((String) t.c1, -(valueWidths[i] + PAD), ty);
+ }
+ if (t.c2 != null && ((String) t.c2).length() > 0) {
+ g.drawString((String) t.c2, unitX, ty);
+ }
+
+ y -= rowHeight;
+ }
+
+ if (!titleString.trim().isEmpty()) {
+ int titleX = (int) (labelX - PAD + (bgRect.getWidth() - titleWidth) * 0.5);
+ g.drawString(titleString, titleX, (float) y);
+ }
+ }
+
+ private Rectangle2D confineIn(Rectangle2D r, Rectangle2D bounds) {
+ // Handle right/bottom overrun
+ double maxX = Math.min(r.getMaxX(), bounds.getMaxX());
+ double maxY = Math.min(r.getMaxY(), bounds.getMaxY());
+ r.setFrame(
+ maxX - r.getWidth(),
+ maxY - r.getHeight(),
+ r.getWidth(),
+ r.getHeight());
+
+ // Handle left/top overrun
+ r.setFrame(
+ Math.max(r.getMinX(), bounds.getMinX()),
+ Math.max(r.getMinY(), bounds.getMinY()),
+ r.getWidth(),
+ r.getHeight());
+
+ return r;
+ }
+
+ @Override
+ public Rectangle2D getBoundsInLocal() {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setLabels(List<Tuple3> list) {
+ this.labels = Lists.reverse(list);
+ }
+
+ public void setOrigin(Point2D origin) {
+ this.origin = origin;
+ }
+
+ @Override
+ public boolean hover(boolean hover, boolean isConnectionTool) {
+// hover = hover && activeNode.updateAndGet(current -> current == null ? this : current) == this;
+ boolean changed = hover != this.hover;
+ this.hover = hover;
+
+// if (changed) {
+// if (!hover) activeNode.updateAndGet(current -> current == this ? null : current);
+// repaint();
+// }
+
+ return changed;
+ }
+
+ @Override
+ public void setMousePosition(Point2D p) {
+ mousePosition = p;
+ }
+
+ @Override
+ public void delete() {
+ super.delete();
+ activeNode.set(null);
+ }
public void hover2(G2DParentNode hoveredNode) {
ParentNode<?> root = (ParentNode<?>) NodeUtil.getNearestParentOfType(parent, RTreeNode.class);
if (root != null) {
-
+
INode child = ProfileVariables.browseChild(root, "");
if(child == null) {
throw new NullPointerException("Scenegraph child node was not found: " + "");