diff --git a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TreeNodeViewApp.java b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TreeNodeViewApp.java index 3fae9a02..ad519150 100644 --- a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TreeNodeViewApp.java +++ b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TreeNodeViewApp.java @@ -146,7 +146,9 @@ public LinkStrategy fromString(String string) { scrollPane.setPrefWidth(220); scrollPane.setFitToWidth(true); parent.setRight(scrollPane); - primaryStage.setScene(new Scene(parent, 1280, 800)); + Scene scene = new Scene(parent, 1280, 800); + scene.getStylesheets().add(TreeNodeViewApp.class.getResource("tree-node-view-app.css").toExternalForm()); + primaryStage.setScene(scene); primaryStage.show(); CSSFX.start(); @@ -244,16 +246,19 @@ public static TreeNode createTree2() { TreeNode root = new TreeNode<>("DO155 for DO"); TreeNode node1 = new TreeNode<>("DO153 for DO"); - node1.setExpanded(false); + //node1.setExpanded(false); TreeNode node2 = new TreeNode<>("DO155 for MOP"); - node2.setExpanded(false); + //node2.setExpanded(false); TreeNode node3 = new TreeNode<>("DO155 for DC"); - node3.setExpanded(false); + //node3.setExpanded(false); root.getChildren().addAll(node1, node2, node3); TreeNode node11 = new TreeNode<>("D0011 from DA"); + node11.setName("da"); TreeNode node12 = new TreeNode<>("D0011 from MOP"); + node12.setName("mop"); TreeNode node13 = new TreeNode<>("D0011 from DC"); + node13.setName("dc"); node1.getChildren().add(node11); node2.getChildren().add(node12); @@ -261,9 +266,14 @@ public static TreeNode createTree2() { TreeNode node121 = new TreeNode<>("Agent Concensus"); - node121.setExpanded(false); + node121.setName("agent"); + //node121.setExpanded(false); node12.getChildren().add(node121); + node11.getLinkedNodes().addAll(node121); + node13.getLinkedNodes().add(node121); + + TreeNode node1211 = new TreeNode<>("D0148 for MOP"); TreeNode node1212 = new TreeNode<>("D0148 for DC"); diff --git a/gemsfx-demo/src/main/resources/com/dlsc/gemsfx/demo/tree-node-view-app.css b/gemsfx-demo/src/main/resources/com/dlsc/gemsfx/demo/tree-node-view-app.css new file mode 100644 index 00000000..bb74ad8d --- /dev/null +++ b/gemsfx-demo/src/main/resources/com/dlsc/gemsfx/demo/tree-node-view-app.css @@ -0,0 +1,11 @@ +.tree-node-view .tree-content > .link-mop-agent.link-path, +.tree-node-view .tree-content > .link-extra-da-agent.link-line, +.tree-node-view .tree-content > .link-extra-dc-agent.link-line { + -fx-stroke: #DB5852; +} + +.tree-node-view .tree-content > .link-mop-agent.link-arrow, +.tree-node-view .tree-content > .link-extra-da-agent.link-arrow, +.tree-node-view .tree-content > .link-extra-dc-agent.link-arrow { + -fx-background-color: #DB5852; +} \ No newline at end of file diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNode.java b/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNode.java index a0f9ac51..ab471424 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNode.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNode.java @@ -43,6 +43,33 @@ public TreeNode(T value) { setValue(value); } + /** + * Represents the name identifier for this node. + * ------------------------------------------------ + * The primary purpose of the 'name' is to assist in determining the style of the node and its links. + * For instance: + * 1. If the node's name is 'n', then the style class for the node would be 'node-n'. + * 2. For a regular parent-child relationship, if a node with name 'n' has a parent named 'm', + * the link connecting them will have a style class 'link-m-n'. + * 3. For any extra links, say from node 'p' to node 'n', the style class for the link would be 'link-extra-p-n'. + * ------------------------------------------------ + * Important considerations: + * - If the 'name' is null, all the above rules become invalid. + * - Both the relevant node and the current node must possess non-null 'name' values for these styling rules to apply. + * ------------------------------------------------ + * This naming convention aids in providing a systematic approach for styling, making it directly + * relatable to the node's relationship and connection type. + */ + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + private final ReadOnlyObjectWrapper> parent = new ReadOnlyObjectWrapper<>(this, "parent", null); public ReadOnlyObjectProperty> parentProperty() { diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNodeViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNodeViewSkin.java index 21875449..d485b530 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNodeViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/treeview/TreeNodeViewSkin.java @@ -43,6 +43,9 @@ public class TreeNodeViewSkin extends SkinBase> { private final Map levelToMaxDimensionMap = new HashMap<>(); private final List> currentLevelNodesCache = new ArrayList<>(); + + private List additionalLinkedNodeList = new ArrayList<>(); + private final Group contentGroup = new Group(); public TreeNodeViewSkin(TreeNodeView view) { @@ -89,11 +92,14 @@ private void addListenersToNode(TreeNode node) { node.getChildren().addListener(invalidationListener); node.widthProperty().addListener(invalidationListener); node.heightProperty().addListener(invalidationListener); + node.getLinkedNodes().addListener(invalidationListener); // Add listeners to the new nodes (if any) and remove listeners from removed nodes (if any) when they are added/removed to/from the children list. ListChangeListener> nodeListChangeListener = createNodeListChangeListener(); childrenListListenerMap.put(node, nodeListChangeListener); node.getChildren().addListener(nodeListChangeListener); + node.getLinkedNodes().addListener(nodeListChangeListener); + } private void removeListenersFromNode(TreeNode removedNode) { @@ -107,11 +113,13 @@ private void removeListenersFromNode(TreeNode removedNode) { removedNode.getChildren().removeListener(invalidationListener); removedNode.widthProperty().removeListener(invalidationListener); removedNode.heightProperty().removeListener(invalidationListener); + removedNode.getLinkedNodes().removeListener(invalidationListener); } ListChangeListener> nodeListChangeListener = childrenListListenerMap.remove(removedNode); if (nodeListChangeListener != null) { removedNode.getChildren().removeListener(nodeListChangeListener); + removedNode.getLinkedNodes().removeListener(nodeListChangeListener); } nodeToComponentsMap.remove(removedNode); @@ -157,6 +165,7 @@ private void buildTree() { if (root != null) { calculatePositions(root); drawNode(root); + drawAdditionalLinkedNodes(); } else { contentGroup.getChildren().setAll(getSkinnable().getPlaceholder()); } @@ -261,6 +270,10 @@ private void drawNode(TreeNode node) { return; } List nodes = view.getLinkStrategy().drawNodeLink(getSkinnable().getLayoutDirection(), levelToMaxDimensionMap.get(node.getLevel()), parent, parentPoint, computeNodeWidth(parent), computeNodeHeight(parent), node, point, computeNodeWidth(node), computeNodeHeight(node), view.getNodeLineGap(), view.getVgap(), view.getHgap()); + if (parent.getName() != null && node.getName() != null) { + nodes.forEach(n -> n.getStyleClass().add("link-" + parent.getName() + "-" + node.getName())); + } + contentGroup.getChildren().addAll(nodes); nodes.add(cell); nodeToComponentsMap.put(node, nodes); @@ -696,6 +709,29 @@ private void updateTree() { if (root != null) { calculatePositions(root); drawNode(root); + drawAdditionalLinkedNodes(); + } + } + + private void drawAdditionalLinkedNodes() { + TreeNode root = getSkinnable().getRoot(); + if (root != null) { + root.stream().forEach(this::drawLinksForNode); + } + } + + private void drawLinksForNode(TreeNode node) { + Point2D sourcePosition = nodeToPositionMap.get(node); + for (TreeNode linkedNode : node.getLinkedNodes()) { + Point2D targetPosition = nodeToPositionMap.get(linkedNode); + if (sourcePosition != null && targetPosition != null) { + List nodes = getSkinnable().getLinkStrategy().drawNodeLink(getSkinnable().getLayoutDirection(), levelToMaxDimensionMap.get(node.getLevel()), node, sourcePosition, computeNodeWidth(node), computeNodeHeight(node), linkedNode, targetPosition, computeNodeWidth(linkedNode), computeNodeHeight(linkedNode), getSkinnable().getNodeLineGap(), getSkinnable().getVgap(), getSkinnable().getHgap()); + if (node.getName() != null && linkedNode.getName() != null) { + nodes.forEach(n -> n.getStyleClass().add("link-extra-" + node.getName() + "-" + linkedNode.getName())); + } + contentGroup.getChildren().addAll(nodes); + additionalLinkedNodeList.addAll(nodes); + } } } @@ -704,6 +740,7 @@ private void updateTree() { */ private void clearMapsForUpdate() { nodeToComponentsMap.clear(); + additionalLinkedNodeList.clear(); nodeToPositionMap.clear(); nodeTotalDimensionMap.clear(); levelToMaxDimensionMap.clear();