package io.swtc.proccessing; import javax.swing.*; import java.awt.*; import java.awt.geom.CubicCurve2D; /** * Acts as a transparent layer over the entire window to draw connections. */ public class ConnectionOverlay extends JComponent { private final Component source; private final Component target; private final Color connectionColor; public ConnectionOverlay(Component source, Component target, Color color) { this.source = source; this.target = target; this.connectionColor = color; setOpaque(false); // Make sure we can see through it } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (!source.isShowing() || !target.isShowing()) return; Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 1. Get absolute positions relative to this Overlay Point p1 = SwingUtilities.convertPoint(source, 0, 0, this); Point p2 = SwingUtilities.convertPoint(target, 0, 0, this); int x1, y1, x2, y2, ctrl1X, ctrl2X; // Calculate vertical centers y1 = p1.y + (source.getHeight() / 2); y2 = p2.y + (target.getHeight() / 2); // 2. Logic to determine Left/Right orientation // If source is to the left of target if (p1.x + source.getWidth() < p2.x) { x1 = p1.x + source.getWidth(); // Right edge of source x2 = p2.x; // Left edge of target } // If source is to the right of target else if (p1.x > p2.x + target.getWidth()) { x1 = p1.x; // Left edge of source x2 = p2.x + target.getWidth(); // Right edge of target } // If they are overlapping horizontally, use centers else { x1 = p1.x + (source.getWidth() / 2); x2 = p2.x + (target.getWidth() / 2); } // 3. Dynamic Curve "Stiffness" // The horizontal distance between the two points determines how far the curve pulls int horizontalDist = Math.abs(x1 - x2); int offset = Math.max(horizontalDist / 2, 20); // Minimum 20px pull for short distances if (x1 < x2) { ctrl1X = x1 + offset; ctrl2X = x2 - offset; } else { ctrl1X = x1 - offset; ctrl2X = x2 + offset; } // 4. Draw the Curve g2d.setColor(connectionColor); g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // We keep the Y coordinates for controls the same as the endpoints // to create that "horizontal entry/exit" look. CubicCurve2D curve = new CubicCurve2D.Float(x1, y1, ctrl1X, y1, ctrl2X, y2, x2, y2); g2d.draw(curve); g2d.dispose(); } }