From: Jussi Koskela Date: Tue, 7 Apr 2020 09:26:23 +0000 (+0300) Subject: Configurable connection crossing styles X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F74%2F4474%2F1;p=simantics%2Fplatform.git Configurable connection crossing styles gitlab #515 Change-Id: I859ce915743c13c37be9ca13cdb0c71a2f077d87 (cherry picked from commit 8ded56d0a440f78cbf649b1e59b8a464e8650fdc) --- diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java new file mode 100644 index 000000000..3ccb492b6 --- /dev/null +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2020 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.diagram.connection.rendering; + +import java.awt.geom.Arc2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ConnectionCrossings implements PathModifier { + public enum Type { + NONE, + GAP, + ARC, + SQUARE + } + + private List segments = new ArrayList<>(); + private double width; + private Type type; + + public void setWidth(double gapWidth) { + this.width = gapWidth; + } + + public double getWidth() { + return width; + } + + public void setType(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + static class Segment { + public double x1, y1, x2, y2; + + public Segment(double x1, double y1, double x2, double y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + }; + + public void reset() { + segments.clear(); + } + + static Double lineLineIntersection(Segment l1, Segment l2) { + double epsilon = 0.001; + + double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2); + if (d == 0.0) return null; + double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d; + if ((s > epsilon) && (s < 1 - epsilon)) { + double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d; + if ((t > epsilon) && (t < 1 - epsilon)) { + return t; + } + } + return null; + } + + public Path2D modify(Path2D path) { + Path2D.Double path2 = new Path2D.Double(); + PathIterator iter = path.getPathIterator(null); + + while (!iter.isDone()) { + + double c[] = new double[6]; + int i = iter.currentSegment(c); + switch (i) { + case PathIterator.SEG_MOVETO: + path2.moveTo(c[0], c[1]); + break; + case PathIterator.SEG_LINETO: + Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]); + + List gaps = new ArrayList<>(); + for (Segment old : segments) { + Double t = lineLineIntersection(old, l); + if (t != null) { + gaps.add(t); + } + } + + if (gaps.isEmpty()) { + path2.lineTo(c[0], c[1]); + } else { + Collections.sort(gaps); + double dx = l.x2 - l.x1; + double dy = l.y2 - l.y1; + + double pos = 0.0; + double len = Math.sqrt(dx*dx + dy*dy); + + boolean finish = true; + Point2D prevGapEnd = null; + for (Double gapCenter : gaps) { + double pos2 = gapCenter - width / 2 / len; + double pos3 = gapCenter + width / 2 / len; + if (pos2 > pos) { + handleGap(path2, prevGapEnd); + prevGapEnd = null; + path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy); + } + if (pos3 < 1.0) { + double x = l.x1 + pos3 * dx; + double y = l.y1 + pos3 * dy; + prevGapEnd = new Point2D.Double(x, y); + } else { + finish = false; + } + pos = pos3; + } + + if (finish) { + handleGap(path2, prevGapEnd); + path2.lineTo(l.x2, l.y2); + } else { + prevGapEnd = new Point2D.Double(l.x2, l.y2); + handleGap(path2, prevGapEnd); + } + } + segments.add(l); + + break; + case PathIterator.SEG_QUADTO: + // TODO: implement gaps + path2.quadTo(c[0], c[1], c[2], c[3]); + break; + case PathIterator.SEG_CUBICTO: + // TODO: implement gaps + path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]); + break; + case PathIterator.SEG_CLOSE: + // TODO: implement gaps + path2.closePath(); + break; + default: + throw new RuntimeException("Unexpected segment type " + i); + } + iter.next(); + } + return path2; + } + + private void handleGap(Path2D path, Point2D prevGapEnd) { + if (prevGapEnd != null) { + switch (type) { + case ARC: + arcTo(path, prevGapEnd.getX(), prevGapEnd.getY()); + break; + case SQUARE: + squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width); + break; + case GAP: + path.moveTo(prevGapEnd.getX(), prevGapEnd.getY()); + break; + case NONE: + break; + } + } + } + + private static void arcTo(Path2D path, double x2, double y2) { + Arc2D arc = new Arc2D.Double(); + double x1 = path.getCurrentPoint().getX(); + double y1 = path.getCurrentPoint().getY(); + double dx = x2 - x1; + double dy = y2 - y1; + double r = Math.sqrt(dx * dx + dy * dy) / 2; + double angle = Math.atan2(dx, dy) * 180 / Math.PI + 90; + double span = (angle > 225 || angle < 45) ? 180 : -180; + arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN); + path.append(arc, true); + } + + private static void squareTo(Path2D path, double x2, double y2, double width) { + double x1 = path.getCurrentPoint().getX(); + double y1 = path.getCurrentPoint().getY(); + double dx = x2 - x1; + double dy = y2 - y1; + double l = Math.sqrt(dx * dx + dy* dy); + if (l > 0) { + double nx = -dy / l; + double ny = dx / l; + if (nx - ny < 0) { + nx = -nx; + ny = -ny; + } + path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2); + path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2); + path.lineTo(x2, y2); + } + } + +} diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionRenderingHints.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionRenderingHints.java new file mode 100644 index 000000000..a78bec04b --- /dev/null +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionRenderingHints.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2020 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.diagram.connection.rendering; + +import java.awt.RenderingHints.Key; + +public final class ConnectionRenderingHints { + + public static final Key KEY_PATH_MODIFIER = new Key(0) { + @Override + public boolean isCompatibleValue(Object val) { + return val == null || val instanceof PathModifier; + } + }; +} \ No newline at end of file diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/PathModifier.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/PathModifier.java new file mode 100644 index 000000000..50d771a40 --- /dev/null +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/PathModifier.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2020 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.diagram.connection.rendering; + +import java.awt.geom.Path2D; + +public interface PathModifier { + public Path2D modify(Path2D source); +} diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/StyledRouteGraphRenderer.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/StyledRouteGraphRenderer.java index dd9473042..fa1447469 100644 --- a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/StyledRouteGraphRenderer.java +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/StyledRouteGraphRenderer.java @@ -62,6 +62,10 @@ public class StyledRouteGraphRenderer implements IRouteGraphRenderer, Serializab path.reset(); rg.getPath2D(path); + PathModifier pm = (PathModifier) g.getRenderingHint(ConnectionRenderingHints.KEY_PATH_MODIFIER); + if (pm != null) { + path = pm.modify(path); + } style.drawPath(g, path, false); branchPoints.clear(); diff --git a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph index b26bfc698..6ea207f88 100644 --- a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph +++ b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph @@ -68,6 +68,9 @@ DIA.IOTableRename -- DIA.Diagram.IOTableRenaming --> DIA.IOTableRename DIA.ProfileEntryContribution.HasEntry --> DIA.ProfileEntry >(diagram){ + + @Override + public Pair perform(ReadGraph graph) throws DatabaseException { + DiagramResource DIA = DiagramResource.getInstance(graph); + Double gap = graph.getPossibleRelatedValue(diagram, DIA.ConnectionCrossingStyle_Width); + Resource typeRes = graph.getPossibleObject(diagram, DIA.ConnectionCrossingStyle_HasType); + ConnectionCrossings.Type type; + if (DIA.ConnectionCrossingStyle_Type_Gap.equals(typeRes)) { + type = ConnectionCrossings.Type.GAP; + } else if (DIA.ConnectionCrossingStyle_Type_Arc.equals(typeRes)) { + type = ConnectionCrossings.Type.ARC; + } else if (DIA.ConnectionCrossingStyle_Type_Square.equals(typeRes)) { + type = ConnectionCrossings.Type.SQUARE; + } else { + type = ConnectionCrossings.Type.NONE; + } + return new Pair<>(gap, type); + } + + }, listener); + } + } + + @Override + public void removedFromContext(ICanvasContext ctx) { + if (listener != null) { + listener.dispose(); + listener = null; + } + super.removedFromContext(ctx); + } + + class ConnectionCrossingStyleListener implements Listener> { + ICanvasContext context; + public ConnectionCrossingStyleListener(ICanvasContext context) { + this.context = context; + } + @Override + public void execute(final Pair result) { + context.getThreadAccess().asyncExec(new Runnable() { + @Override + public void run() { + ICanvasContext ctx = context; + if (ctx == null) + return; + if (ctx.isDisposed()) + return; + crossings.setWidth(result.first != null ? result.first : 0.0); + crossings.setType(result.second); + ccNode.repaint(); + } + }); + } + public void dispose() { + context = null; + } + @Override + public boolean isDisposed() { + return context == null || context.isDisposed(); + } + @Override + public void exception(Throwable t) { + ErrorLogger.defaultLogError(t); + } + } + +} diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/DiagramViewer.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/DiagramViewer.java index 409aecbea..489ebd6c7 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/DiagramViewer.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/DiagramViewer.java @@ -67,6 +67,7 @@ import org.simantics.diagram.handler.DeleteHandler; import org.simantics.diagram.handler.ExpandSelectionHandler; import org.simantics.diagram.handler.SimpleElementTransformHandler; import org.simantics.diagram.layer.ILayersViewPage; +import org.simantics.diagram.participant.ConnectionCrossingsParticipant; import org.simantics.diagram.participant.ContextUtil; import org.simantics.diagram.participant.PointerInteractor2; import org.simantics.diagram.participant.SGFocusParticipant; @@ -330,6 +331,7 @@ public class DiagramViewer //ctx.add(new ZoomTransitionParticipant(TransitionFunction.SIGMOID)); //ctx.add(new TooltipParticipant()); ctx.add(new TerminalTooltipParticipant()); + ctx.add(new ConnectionCrossingsParticipant(getInputResource())); } protected void addPainterParticipants(ICanvasContext ctx) { diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/sg/DiagramSceneGraphProvider.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/sg/DiagramSceneGraphProvider.java index 3ca0cf698..302e074fc 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/sg/DiagramSceneGraphProvider.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/sg/DiagramSceneGraphProvider.java @@ -36,6 +36,7 @@ import org.simantics.diagram.handler.CopyPasteStrategy; import org.simantics.diagram.handler.DefaultCopyPasteStrategy; import org.simantics.diagram.handler.DeleteHandler; import org.simantics.diagram.handler.SimpleElementTransformHandler; +import org.simantics.diagram.participant.ConnectionCrossingsParticipant; import org.simantics.diagram.query.DiagramRequests; import org.simantics.diagram.runtime.RuntimeDiagramManager; import org.simantics.diagram.stubs.DiagramResource; @@ -251,6 +252,7 @@ public class DiagramSceneGraphProvider implements ICanvasSceneGraphProvider, IDi ctx.add( new Selection() ); ctx.add( new DiagramParticipant() ); ctx.add( new ElementPainter(true) ); + ctx.add( new ConnectionCrossingsParticipant(resource)); //ctx.add( new ElementHeartbeater() ); ctx.add( new ZOrderHandler() ); diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionCrossingsNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionCrossingsNode.java new file mode 100644 index 000000000..a46c4e748 --- /dev/null +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionCrossingsNode.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2020 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.scenegraph.g2d.nodes; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +import org.simantics.diagram.connection.rendering.ConnectionCrossings; +import org.simantics.diagram.connection.rendering.ConnectionRenderingHints; +import org.simantics.scenegraph.g2d.G2DNode; + +public class ConnectionCrossingsNode extends G2DNode { + + private static final long serialVersionUID = -696142275610396889L; + + private ConnectionCrossings crossings; + + @Override + public void render(Graphics2D g) { + crossings.reset(); + if (crossings.getWidth() > 0 && crossings.getType() != ConnectionCrossings.Type.NONE) { + g.setRenderingHint(ConnectionRenderingHints.KEY_PATH_MODIFIER, crossings); + } else { + g.setRenderingHint(ConnectionRenderingHints.KEY_PATH_MODIFIER, null); + } + } + + public void setCrossings(ConnectionCrossings crossings) { + this.crossings = crossings; + } + + @Override + public Rectangle2D getBoundsInLocal() { + return null; + } + + public ConnectionCrossings getConnectionCrossings() { + return crossings; + } +}