diff options
Diffstat (limited to 'src/jcgp/gui/population/GUINode.java')
-rw-r--r-- | src/jcgp/gui/population/GUINode.java | 494 |
1 files changed, 416 insertions, 78 deletions
diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java index 1a32426..4d420ea 100644 --- a/src/jcgp/gui/population/GUINode.java +++ b/src/jcgp/gui/population/GUINode.java @@ -1,134 +1,472 @@ package jcgp.gui.population; +import javafx.event.EventHandler; +import javafx.scene.control.Label; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.paint.Paint; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; -import jcgp.backend.population.Gene; +import jcgp.backend.function.Function; +import jcgp.backend.population.Connection; +import jcgp.backend.population.Input; import jcgp.backend.population.Node; +import jcgp.backend.resources.Resources; import jcgp.gui.GUI; import jcgp.gui.constants.Constants; -import jcgp.gui.constants.Position; -import jcgp.gui.handlers.NodeHandlers; -/** - * The GUI counterpart of {@link jcgp.backend.population.Node}. This is a - * subclass of {@code GUIGene} which represents a chromosome node. - * - * @author Eduardo Pedroni - */ -public class GUINode extends GUIGene implements GUIMutable, GUIConnection { +public class GUINode extends GUIGene { - private Node node; private Line[] lines; - private Circle[] sockets; + private Node node; + private Resources resources; + private int connectionIndex = 0; - /** - * Instantiate {@code GUINode} given a {@code Node} and the lines needed - * to show its connections. - * - * @param node the associated backend node. - * @param lines the lines used to display connections. - */ - public GUINode(Node node, Line[] lines) { + + public GUINode(ChromosomePane parentRef, final Node node, Line[] connectionLines, final GUI gui) { super(); - // store references, associate with node + + // store references + this.parent = parentRef; this.node = node; - this.lines = lines; - node.setGUIObject(this); + this.lines = connectionLines; + this.resources = gui.getExperiment().getResources(); + + // move the GUIGene to the right position + relocate(((node.getColumn() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS, + (node.getRow() * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS); + + // set the line ends correctly + updateLines(); - // create the output socket - Circle output = new Circle(Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Constants.SOCKET_PAINT); + final Label connectionNumber = new Label(); + connectionNumber.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); "); + connectionNumber.setVisible(false); + + Circle output = new Circle(Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Paint.valueOf("white")); output.setStroke(Paint.valueOf("black")); - - // create input sockets - sockets = new Circle[GUI.resources.arity()]; + + updateText(); + + Circle[] sockets = new Circle[resources.arity()]; + double angle, xPos, yPos; for (int l = 0; l < sockets.length; l++) { - sockets[l] = new Circle(Constants.SOCKET_RADIUS, Constants.SOCKET_PAINT); - sockets[l].setStroke(Paint.valueOf("black")); + angle = (((l + 1) / ((double) (resources.arity() + 1))) * Constants.THETA) - (Constants.THETA / 2); + xPos = -Math.cos(angle) * Constants.NODE_RADIUS; + yPos = Math.sin(angle) * Constants.NODE_RADIUS; + + sockets[l] = new Circle(xPos, yPos, Constants.SOCKET_RADIUS, Paint.valueOf("white")); sockets[l].setId(String.valueOf(l)); - // relocate them - Position.placeSocket(l, sockets[l]); - Position.connect(lines[l], (GUIGene) ((Gene) node.getConnection(l)).getGUIObject()); + sockets[l].setStroke(Paint.valueOf("black")); + + final Circle s = sockets[l]; + final int index = l; + + /* + * Mouse event handlers on sockets + * + */ + s.addEventFilter(MouseEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // the mouse has been dragged out of the socket, this means a full drag is in progress + startFullDrag(); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user is hovering over connection socket + connectionNumber.setText("C: " + s.getId()); + connectionNumber.relocate(s.getCenterX() + 5, s.getCenterY() - 10); + connectionNumber.setVisible(true); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionNumber.setVisible(false); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + setState(GUIGeneState.SOURCE); + connectionIndex = index; + } + }); + + s.addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (!parent.isTarget()) { + lines[connectionIndex].setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX()); + lines[connectionIndex].setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY()); + } + } + }); + + s.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.isStillSincePress()) { + // mouse was released before dragging out of the socket + updateLine(index); + setState(GUIGeneState.HOVER); + } else if (getState() == GUIGeneState.SOURCE) { + // no connection has been made, fallback + resetState(); + updateLines(); + } + } + }); } + + /* + * Mouse event handlers on whole gene + */ + addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + gui.bringFunctionSelector(event, (GUINode) event.getSource()); + } + }); - // add elements + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has entered this node, react appropriately + // this happens even if we are the source of the drag + if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) { + ((GUIGene) event.getGestureSource()).setConnectionLine((GUIGene) event.getSource()); + + Connection source = ((GUIGene) event.getGestureSource()).getChangingConnection(); + if (node == source) { + setState(GUIGeneState.NO_CHANGE_TARGET); + } else { + setState(GUIGeneState.VALID_TARGET); + } + } else { + setState(GUIGeneState.INVALID_TARGET); + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has exited this node, react appropriately + // this happens even if we are the source of the drag + parent.setTarget(false); + if (event.isPrimaryButtonDown()) { + if (event.getGestureSource() == event.getSource()) { + setState(GUIGeneState.SOURCE); + } else { + if (getState() == GUIGeneState.NO_CHANGE_TARGET) { + setState(GUIGeneState.INDIRECT_HOVER); + } else { + setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnectionStates(GUIGeneState.INDIRECT_HOVER); + } + } + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + GUIGene source = ((GUIGene) event.getGestureSource()); + // set states to reflect the new situation + if (source.isLocked()) { + source.setState(GUIGeneState.HOVER); + source.setConnectionStates(GUIGeneState.HOVER); + } else { + source.setState(GUIGeneState.NEUTRAL); + source.setConnectionStates(GUIGeneState.NEUTRAL); + } + + // the user released the drag gesture on this node, react appropriately + if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) { + if (source.isLocked()) { + // remove locks from the old connection, add the to setConnethe new + // note that the old connection may still have locks after this + parent.getGuiGene(source.getChangingConnection()).removeLocks(source.getLocks()); + addLocks(source.getLocks()); + } else { + if (source instanceof GUIOutput) { + source.resetState(); + } + } + source.setChangingConnection(node); + + } + source.updateLines(); + setState(GUIGeneState.HOVER); + } + }); + + addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has entered this node without dragging, or it is dragging and this is the source + if (getState() == GUIGeneState.NEUTRAL) { + setState(GUIGeneState.HOVER); + } else if (locked > 0) { + setConnectionStates(GUIGeneState.LOCKED_HOVER); + } + } + }); + + addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has left this node without dragging, or it is dragging and this is the source + if (getState() == GUIGeneState.HOVER && locked <= 0) { + setState(GUIGeneState.NEUTRAL); + setConnectionStates(GUIGeneState.NEUTRAL); + } else if (locked > 0) { + if (getState() == GUIGeneState.SOURCE || getState() == GUIGeneState.INVALID_TARGET) { + setConnectionStates(GUIGeneState.INDIRECT_HOVER); + } else { + setConnectionStates(GUIGeneState.HOVER); + } + + } + } + }); + + getChildren().addAll(mainCircle, text); getChildren().addAll(sockets); - getChildren().add(output); - - // relocate node, add handlers - Position.place(this); - NodeHandlers.addHandlers(this); + getChildren().addAll(output, connectionNumber); + + } + + @Override + public void setState(GUIGeneState newState) { + switch (newState) { + case ACTIVE_HOVER: + if (locked > 0) { + setState(GUIGeneState.LOCKED_HOVER); + } else { + mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR)); + showLines(true); + } + setConnectionStates(GUIGeneState.ACTIVE_HOVER); + break; + case LOCKED_HOVER: + mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR)); + break; + case INVALID_TARGET: + mainCircle.setFill(Paint.valueOf(Constants.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(Constants.MEDIUM_HIGHLIGHT_COLOUR)); + showLines(true); + if (locked <= 0) { + setConnectionStates(GUIGeneState.INDIRECT_HOVER); + } else { + setConnectionStates(GUIGeneState.HOVER); + } + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + if (locked > 0) { + setState(GUIGeneState.HOVER); + } else { + mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_COLOUR)); + showLines(false); + if (getState() == GUIGeneState.ACTIVE_HOVER) { + setConnectionStates(GUIGeneState.NEUTRAL); + } + } + break; + case NO_CHANGE_TARGET: + parent.setTarget(true); + mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(Constants.HARD_HIGHLIGHT_COLOUR)); + break; + case VALID_TARGET: + parent.setTarget(true); + mainCircle.setFill(Paint.valueOf(Constants.GOOD_SELECTION_COLOUR)); + break; + default: + break; + } + + super.setState(newState); } - /** - * @return the {@code Node} instance associated with this object. - */ + @Override + public Connection getChangingConnection() { + return node.getConnection(connectionIndex); + } + + private boolean isAllowed(GUIGene source, GUIGene target) { + if (source instanceof GUINode) { + // if the source is a node, all inputs and some nodes are valid + if (target instanceof GUIInput) { + return true; + } else if (target instanceof GUINode) { + // target and source are nodes, let's look at levels back + Node t = ((GUINode) target).getNode(), s = ((GUINode) source).getNode(); + if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= resources.levelsBack()) { + return true; + } + return false; + } else if (target instanceof GUIOutput) { + return false; + } else { + throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput."); + } + } else if (source instanceof GUIOutput) { + // if the source is an output, any node or input is valid + if (target instanceof GUINode || target instanceof GUIInput) { + return true; + } else if (target instanceof GUIOutput) { + return false; + } else { + throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput."); + } + } + // if the source was neither node nor output, something bad is happening + throw new ClassCastException("Source was neither GUINode nor GUIOutput."); + } + + public Node getNode() { return node; } /** - * Associates this instance with a new node. + * Place the end of the specified line on the output of the associated connection. * - * @param node the new node. + * @param index the line to be updated. */ - void setNode(Node node) { - this.node = node; + public void updateLine(int index) { + if (node.getConnection(index) instanceof Node) { + int row = ((Node) node.getConnection(index)).getRow(), + column = ((Node) node.getConnection(index)).getColumn(); + lines[index].setEndX(((column + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + 2 * Constants.NODE_RADIUS); + lines[index].setEndY((row * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS); + } else if (node.getConnection(index) instanceof Input) { + int inputIndex = ((Input) node.getConnection(index)).getIndex(); + lines[index].setEndX(2 * Constants.NODE_RADIUS); + lines[index].setEndY(inputIndex * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.NODE_RADIUS); + } } - + + /** + * Updates the end of all lines to match the associated connections. + */ @Override - public Line[] getLines() { - return lines; + public void updateLines() { + for (int c = 0; c < lines.length; c++) { + updateLine(c); + } } - + /** - * Returns one of this object's connection sockets. They are - * indexed in the same order as lines and the connections - * they represent. + * Toggle visibility of all connection lines. * - * @param index the socket to return. - * @return the indexed socket object. + * @param value whether to show the lines or not. */ - public Circle getSocket(int index) { - return sockets[index]; + private void showLines(boolean value) { + for (int i = 0; i < lines.length; i++) { + lines[i].setVisible(value); + } } - - /** - * @return the entire {@code Socket} array. - */ - public Circle[] getSockets() { - return sockets; + + @Override + public void setConnectionStates(GUIGeneState newState) { + for (int i = 0; i < lines.length; i++) { + parent.getGuiGene(node.getConnection(i)).setState(newState); + } + } + + @Override + public void setChangingConnection(Connection newConnection) { + node.setConnection(connectionIndex, newConnection); + if (parent.isEvaluating()) { + parent.updateValues(); + } } + @Override - public void setStateRecursively(GUIGeneState state) { - setState(state); - for (int i = 0; i < GUI.resources.arity(); i++) { - ((GUIConnection) ((Gene) node.getConnection(i)).getGUIObject()).setStateRecursively(state); + public void resetState() { + if (locked > 0) { + setState(GUIGeneState.HOVER); + } else { + setState(GUIGeneState.NEUTRAL); + setConnectionStates(GUIGeneState.NEUTRAL); } + } @Override - protected void setLinesVisible(boolean value) { + protected void setLocked(boolean value) { + locked += value ? 1 : -1; + setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER); + for (int i = 0; i < lines.length; i++) { - lines[i].setVisible(value); + parent.getGuiGene(node.getConnection(i)).setLocked(value); + } + } + + @Override + public void addLocks(int value) { + locked += value; + setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER); + + for (int i = 0; i < lines.length; i++) { + parent.getGuiGene(node.getConnection(i)).addLocks(value); } } @Override - public void setLockRecursively(boolean value) { - setLock(value); - for (int i = 0; i < GUI.resources.arity(); i++) { - ((GUIConnection) ((Gene) node.getConnection(i)).getGUIObject()).setLockRecursively(value); + public void removeLocks(int value) { + locked -= value; + setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.NEUTRAL); + + for (int i = 0; i < lines.length; i++) { + parent.getGuiGene(node.getConnection(i)).removeLocks(value); } } @Override - public GUIConnection[] getConnections() { - GUIConnection[] connections = new GUIConnection[GUI.resources.arity()]; - for (int c = 0; c < connections.length; c++) { - connections[c] = (GUIConnection) ((Gene) node.getConnection(c)).getGUIObject(); + public void setConnectionLine(GUIGene gene) { + lines[connectionIndex].setEndX(gene.getLayoutX() + Constants.NODE_RADIUS); + lines[connectionIndex].setEndY(gene.getLayoutY()); + } + + public void updateText() { + if (parent.isEvaluating()) { + text.setText(node.getFunction() + "\n" + node.getValue().toString()); + } else { + text.setText(node.getFunction().toString()); } - return connections; + } + + public void setFunction(Function function) { + node.setFunction(function); + if (parent.isEvaluating()) { + parent.updateValues(); + } else { + updateText(); + } + } + + public void setNode(Node newNode) { + node = newNode; } } |