diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 32f9a5bcbd..bee0a3c512 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -722,14 +722,18 @@ private Collection transformReactorNetwork( // connect input KPort port = null; for (TriggerInstance trigger : reaction.triggers) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); - int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0; - int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0; - if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) { - // manual adjustment disabling automatic one - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, - (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + + int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0; + int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0; + if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) { + // manual adjustment disabling automatic one + setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, + (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); + } } if (trigger.isStartup()) { @@ -764,20 +768,22 @@ private Collection transformReactorNetwork( } // connect dependencies - //port = null // create new ports for (TriggerInstance dep : reaction.sources) { - if (reaction.triggers.contains(dep)) continue; - if (!(getBooleanValue(REACTIONS_USE_HYPEREDGES) && port != null)) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); - int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0; - int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0; - if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) { - // manual adjustment disabling automatic one - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, - (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); - } - } + if (reaction.triggers.contains(dep)) continue; // skip + + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + + int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0; + int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0; + if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) { + // manual adjustment disabling automatic one + setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, + (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); + } + } if (dep instanceof PortInstance) { KPort src = null; @@ -794,11 +800,14 @@ private Collection transformReactorNetwork( } // connect outputs - port = null; // create new ports + port = null; // enforce new ports for outputs Set> iterSet = reaction.effects != null ? reaction.effects : new HashSet<>(); for (TriggerInstance effect : iterSet) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); + } if (effect instanceof ActionInstance) { actionSources.put((ActionInstance) effect, port); diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java index b773364cc4..5181739a93 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java @@ -24,6 +24,16 @@ ***************/ package org.lflang.diagram.synthesis.postprocessor; +import java.util.stream.Collectors; + +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.core.options.PortSide; +import org.eclipse.elk.graph.properties.Property; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Pair; +import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; + import de.cau.cs.kieler.klighd.IStyleModifier; import de.cau.cs.kieler.klighd.IViewer; import de.cau.cs.kieler.klighd.internal.ILayoutRecorder; @@ -33,16 +43,8 @@ import de.cau.cs.kieler.klighd.kgraph.KNode; import de.cau.cs.kieler.klighd.kgraph.KPoint; import de.cau.cs.kieler.klighd.kgraph.KPort; -import java.util.List; -import java.util.Map; -import org.eclipse.elk.core.options.CoreOptions; -import org.eclipse.elk.core.options.PortSide; -import org.eclipse.elk.graph.properties.IProperty; -import org.eclipse.elk.graph.properties.Property; -import org.eclipse.xtext.xbase.lib.Extension; -import org.eclipse.xtext.xbase.lib.IterableExtensions; -import org.eclipse.xtext.xbase.lib.Pair; -import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; +import de.cau.cs.kieler.klighd.krendering.KRendering; +import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; /** * Adjusts the port position of reactions node AFTER layout, to allow free port order but also adapt (snuggle) to pointy shape of reaction node. @@ -57,6 +59,19 @@ public class ReactionPortAdjustment implements IStyleModifier { @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; + private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + /** + * Register this modifier on a reaction rendering. + */ + public static void apply(KNode node, KRendering rendering) { + // Add modifier that fixes port positions such that edges are properly attached to the shape + var invisible = _kRenderingFactory.createKInvisibility(); + invisible.setInvisible(false); // make it ineffective (just for purpose of holding modifier) + invisible.setModifierId(ReactionPortAdjustment.ID); // Add modifier to receive callback after layout + rendering.getStyles().add(invisible); + node.setProperty(PROCESSED, false); + } @Override public boolean modify(IStyleModifier.StyleModificationContext context) { @@ -72,54 +87,40 @@ public boolean modify(IStyleModifier.StyleModificationContext context) { } // Get viewer (this is a bit brittle because it fetches the viewer from some internal property) - Map.Entry, Object> first = IterableExtensions.findFirst( - parent.getAllProperties().entrySet(), - it -> { - return it.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") || - it.getKey().getId().equals("klighd.layout.viewer"); - } - ); - Object viewer = first != null ? first.getValue() : null; - + Object viewer = + parent.getAllProperties().entrySet().stream().filter(entry -> + entry.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") + || entry.getKey().getId().equals("klighd.layout.viewer")) + .findAny().map(entry -> entry.getValue()).orElse(null); + ILayoutRecorder recorder = null; if (viewer instanceof IViewer) { recorder = ((IViewer) viewer).getViewContext().getLayoutRecorder(); } - if (!knode.getPorts().isEmpty()) { if (IterableExtensions.head(knode.getPorts()).getYpos() != 0 && - !knode.getProperty(ReactionPortAdjustment.PROCESSED)) { // Only adjust if layout is already applied - // important for incremental update animation + // Only adjust if layout is already applied important for incremental update animation + !knode.getProperty(ReactionPortAdjustment.PROCESSED)) { if (recorder != null) { recorder.startRecording(); } - List in = IterableExtensions.toList( - IterableExtensions.sortBy( - IterableExtensions.filter( - knode.getPorts(), - it -> { - return it.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST && - !it.hasProperty(CoreOptions.PORT_BORDER_OFFSET); - }), - it -> { return it.getYpos(); }) - ); + var in = knode.getPorts().stream().filter(p -> + p.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST).sorted((p1, p2) -> + Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList()); - List out = IterableExtensions.toList( - IterableExtensions.sortBy( - IterableExtensions.filter( - knode.getPorts(), - it -> { - return it.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST && - !it.hasProperty(CoreOptions.PORT_BORDER_OFFSET); - }), - it -> { return it.getYpos(); }) - ); + var out = knode.getPorts().stream().filter(p -> + p.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST).sorted((p1, p2) -> + Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList()); // Adjust - adjustPositions(IterableExtensions.indexed(in), in.size(), true); - adjustPositions(IterableExtensions.indexed(out), out.size(), false); + if (in.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { + adjustPositions(IterableExtensions.indexed(in), in.size(), true); + } + if (out.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { + adjustPositions(IterableExtensions.indexed(out), out.size(), false); + } knode.setProperty(ReactionPortAdjustment.PROCESSED, true); if (recorder!=null) { @@ -166,6 +167,9 @@ public void adjustPositions(Iterable> indexedPorts, int cou edge.setSourcePoint(adjustedKPoint(edge.getSourcePoint(), offset)); } } + + // Save for future layout + port.setProperty(CoreOptions.PORT_BORDER_OFFSET, (double) (input ? -offset : offset)); } } diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index 7834e8baba..014f93686b 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -343,7 +343,9 @@ public KPolygon addReactionFigure(KNode node, ReactionInstance reaction) { _kRenderingExtensions.createKPosition(LEFT, REACTION_POINTINESS, 0, BOTTOM, 0, 0.5f) ) ); - IterableExtensions.head(baseShape.getStyles()).setModifierId(ReactionPortAdjustment.ID); + + // For a shape like this, ports can only positioned correctly after the layout. + ReactionPortAdjustment.apply(node, baseShape); KRectangle contentContainer = _kContainerRenderingExtensions.addRectangle(baseShape); associateWith(contentContainer, reaction);