]> gerrit.simantics Code Review - simantics/district.git/blobdiff - org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkHoverInfoNode.java
Fixed rendering of district hover info node to always stay inside editor
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / nodes / DistrictNetworkHoverInfoNode.java
index 07ad2eedb4df1decd66f4ac0e965a76a67d6039c..24b7f5898c0dd0a06970ce1b28147c1b623120d2 100644 (file)
@@ -2,14 +2,16 @@ package org.simantics.district.network.ui.nodes;
 
 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;
@@ -21,138 +23,213 @@ import org.simantics.scenegraph.g2d.G2DParentNode;
 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: " + "");