diff options
Diffstat (limited to 'src/jcgp/gui')
-rw-r--r-- | src/jcgp/gui/Console.java | 13 | ||||
-rw-r--r-- | src/jcgp/gui/GUI.java | 14 | ||||
-rw-r--r-- | src/jcgp/gui/SettingsPane.java | 11 | ||||
-rw-r--r-- | src/jcgp/gui/population/ChromosomePane.java | 97 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUIGene.java | 56 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUIInput.java | 193 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUINode.java | 417 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUIOutput.java | 277 |
8 files changed, 1064 insertions, 14 deletions
diff --git a/src/jcgp/gui/Console.java b/src/jcgp/gui/Console.java new file mode 100644 index 0000000..9008531 --- /dev/null +++ b/src/jcgp/gui/Console.java @@ -0,0 +1,13 @@ +package jcgp.gui; + +import javafx.scene.control.TextArea; + +public class Console extends TextArea { + + public Console() { + super(); + setDisable(true); + setPrefHeight(100); + } + +} diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java deleted file mode 100644 index 0e4b91a..0000000 --- a/src/jcgp/gui/GUI.java +++ /dev/null @@ -1,14 +0,0 @@ -package jcgp.gui; - -import javafx.application.Application; -import javafx.stage.Stage; - -public class GUI extends Application { - - @Override - public void start(Stage primaryStage) throws Exception { - - - } - -} diff --git a/src/jcgp/gui/SettingsPane.java b/src/jcgp/gui/SettingsPane.java new file mode 100644 index 0000000..53056d3 --- /dev/null +++ b/src/jcgp/gui/SettingsPane.java @@ -0,0 +1,11 @@ +package jcgp.gui; + +import javafx.scene.layout.VBox; + +public class SettingsPane extends VBox { + + public SettingsPane() { + super(); + setPrefSize(180, 500); + } +} diff --git a/src/jcgp/gui/population/ChromosomePane.java b/src/jcgp/gui/population/ChromosomePane.java new file mode 100644 index 0000000..546314e --- /dev/null +++ b/src/jcgp/gui/population/ChromosomePane.java @@ -0,0 +1,97 @@ +package jcgp.gui.population; + +import java.util.ArrayList; + +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Pane; +import javafx.scene.shape.Line; +import jcgp.CGP.Resources; +import jcgp.population.Chromosome; + + +public class ChromosomePane extends ScrollPane { + + private GUINode[][] guiNodes; + private GUIInput[] guiInputs; + private GUIOutput[] guiOutputs; + + private Pane content; + + private ArrayList<Line> connectionLines; + + public ChromosomePane(Chromosome chromosome, Resources resources) { + super(); + connectionLines = new ArrayList<Line>(); + + content = new Pane(); + content.setId("content pane for genes"); + + // generate the GUIGenes + // inputs + guiInputs = new GUIInput[(int) resources.get("inputs")]; + for (int i = 0; i < guiInputs.length; i++) { + // make the GUI elements + guiInputs[i] = new GUIInput(this, chromosome.getInput(i)); + content.getChildren().addAll(guiInputs[i]); + } + // nodes + guiNodes = new GUINode[(int) resources.get("rows")][(int) resources.get("columns")]; + double angle, xPos, yPos; + for (int r = 0; r < guiNodes.length; r++) { + for (int c = 0; c < guiNodes[r].length; c++) { + // make the connection lines + Line lines[] = new Line[(int) resources.get("arity")]; + for (int l = 0; l < lines.length; l++) { + angle = ((((double) (l + 1)) / ((double) (lines.length + 1))) * GUIGene.THETA) - (GUIGene.THETA / 2); + xPos = (-Math.cos(angle) * GUIGene.NODE_RADIUS) + (((c + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS); + yPos = (Math.sin(angle) * GUIGene.NODE_RADIUS) + ((r * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS); + + lines[l] = new Line(xPos, yPos, xPos, yPos); + lines[l].setMouseTransparent(true); + lines[l].setVisible(false); + connectionLines.add(lines[l]); + } + // make the GUI elements + guiNodes[r][c] = new GUINode(this, chromosome.getNode(r, c), resources, lines); + } + content.getChildren().addAll(guiNodes[r]); + } + + // outputs + guiOutputs = new GUIOutput[(int) resources.get("outputs")]; + for (int i = 0; i < guiOutputs.length; i++) { + xPos = (((int) resources.get("columns") + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)); + yPos = (chromosome.getOutput(i).getIndex() * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS; + // make the line + Line line = new Line(xPos, yPos, xPos, yPos); + line.setMouseTransparent(true); + line.setVisible(false); + connectionLines.add(line); + // make the GUI elements + guiOutputs[i] = new GUIOutput(this, chromosome.getOutput(i), resources, line); + content.getChildren().addAll(guiOutputs[i]); + } + + content.getChildren().addAll(connectionLines); + setPrefWidth(620); + setContent(content); + } + + + public GUINode getGuiNode(int row, int column) { + return guiNodes[row][column]; + } + + public GUIInput getGuiInput(int index) { + return guiInputs[index]; + } + + public GUIOutput getGuiOutput(int index) { + return guiOutputs[index]; + } + + public Pane getContentPane() { + return content; + } + +} diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java new file mode 100644 index 0000000..ef44110 --- /dev/null +++ b/src/jcgp/gui/population/GUIGene.java @@ -0,0 +1,56 @@ +package jcgp.gui.population; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Group; +import javafx.scene.shape.Circle; +import javafx.scene.text.Text; +import jcgp.population.Gene; + +enum GUIGeneState { + NEUTRAL, + HOVER, + INDIRECT_HOVER, + ACTIVE_HOVER, + SOURCE, + TARGET, + NO_CHANGE_TARGET, + FORBIDDEN_TARGET +} + +public abstract class GUIGene extends Group { + + public static final double NODE_RADIUS = 30; + public static final double SPACING = 15; + + public static final double THETA = Math.PI / 1.4; + public static final double SOCKET_RADIUS = Math.sqrt(NODE_RADIUS) / 1.8; + + public static final double NODE_TEXT = 0; + + protected Text text; + protected Circle mainCircle; + + protected SimpleObjectProperty<GUIGeneState> stateProperty = new SimpleObjectProperty<GUIGeneState>(GUIGeneState.NEUTRAL); + + protected ChromosomePane parent; + + public SimpleObjectProperty<GUIGeneState> stateProperty() { + return stateProperty; + } + + public void setState(GUIGeneState newState) { + stateProperty.set(newState); + } + + public void showText(boolean value) { + text.setVisible(value); + } + + public abstract Gene getGene(); + + public abstract void setConnections(GUIGeneState newState); + + public abstract void resetState(); + + +} diff --git a/src/jcgp/gui/population/GUIInput.java b/src/jcgp/gui/population/GUIInput.java new file mode 100644 index 0000000..6f7a523 --- /dev/null +++ b/src/jcgp/gui/population/GUIInput.java @@ -0,0 +1,193 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.GUI; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUIInput extends GUIGene { + + private Circle outputSocket; + private Input input; + + public GUIInput(ChromosomePane parentRef, final Input input) { + + this.parent = parentRef; + this.input = input; + + relocate(NODE_RADIUS, + (input.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text("I: " + input.getIndex()); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + outputSocket = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + outputSocket.setId(String.valueOf(0)); + outputSocket.setStroke(Paint.valueOf("black")); + + getChildren().addAll(mainCircle, text, outputSocket); + + /* + * Mouse event handlers on whole gene + */ + 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 (event.getGestureSource() instanceof GUINode) { + Node source = ((GUINode) event.getGestureSource()).getGene(); + for (int i = 0; i < (int) GUI.resources.get("arity"); i++) { + if (input == source.getConnection(i)) { + stateProperty.set(GUIGeneState.NO_CHANGE_TARGET); + return; + } + } + } else if (event.getGestureSource() instanceof GUIOutput) { + Output source = ((GUIOutput) event.getGestureSource()).getGene(); + if (((GUIGene) event.getSource()).getGene() == source.getSource()) { + ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET); + } + } + stateProperty.set(GUIGeneState.TARGET); + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + 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 + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(GUIGeneState.HOVER); + // the user released the drag gesture on this node, react appropriately + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).setChangingConnection(input); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, input); + } + } + }); + + 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 (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.INDIRECT_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 (stateProperty.get() == GUIGeneState.INDIRECT_HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + switch (newValue) { + case ACTIVE_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + } + + @Override + public Input getGene() { + return input; + } + + /** + * Set all connections to a given state. + * + * @param newState the state to set connections to. + */ + @Override + public void setConnections(GUIGeneState newState) { + // nothing + } + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + + } +} diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java new file mode 100644 index 0000000..4350e52 --- /dev/null +++ b/src/jcgp/gui/population/GUINode.java @@ -0,0 +1,417 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +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 javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.CGP.Resources; +import jcgp.GUI; +import jcgp.function.Function; +import jcgp.population.Connection; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUINode extends GUIGene { + + private Circle[] sockets; + private Circle output; + + private Line[] lines; + + private Label connectionNumber; + + private Node node; + + private int connectionIndex; + + public GUINode(ChromosomePane parentRef, final Node node, Resources resources, Line[] connectionLines) { + + // store references + this.parent = parentRef; + this.node = node; + this.lines = connectionLines; + + // move the GUIGene to the right position + relocate(((node.getColumn() + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS, + (node.getRow() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + // set the line ends correctly + updateLines(); + + connectionNumber = new Label(); + connectionNumber.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); "); + connectionNumber.setVisible(false); + + output = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + output.setStroke(Paint.valueOf("black")); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text(node.getFunction().getName()); + this.node.functionProperty().addListener(new ChangeListener<Function>() { + @Override + public void changed(ObservableValue<? extends Function> observable, + Function oldValue, Function newValue) { + text.setText(newValue.getName()); + } + }); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + sockets = new Circle[(int) resources.get("arity")]; + double angle, xPos, yPos; + for (int l = 0; l < sockets.length; l++) { + angle = ((((double) (l + 1)) / ((double) ((int) resources.get("arity") + 1))) * THETA) - (THETA / 2); + xPos = -Math.cos(angle) * NODE_RADIUS; + yPos = Math.sin(angle) * NODE_RADIUS; + + sockets[l] = new Circle(xPos, yPos, GUIGene.SOCKET_RADIUS, Paint.valueOf("white")); + sockets[l].setId(String.valueOf(l)); + sockets[l].setStroke(Paint.valueOf("black")); + + final Circle s = sockets[l]; + final int index = l; + + /* + * Mouse event handlers on sockets + * + */ + s.addEventFilter(MouseDragEvent.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(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + stateProperty.set(GUIGeneState.SOURCE); + connectionIndex = index; + } + }); + + s.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lines[connectionIndex].setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX()); + lines[connectionIndex].setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY()); + } + }); + + s.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.isDragDetect()) { + // mouse was released before dragging out of the socket + updateLine(index); + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + } + + /* + * Mouse event handlers on whole gene + */ + 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())) { + if (event.getGestureSource() instanceof GUINode) { + Node source = ((GUINode) event.getGestureSource()).getGene(); + for (int i = 0; i < lines.length; i++) { + if (node == source.getConnection(i)) { + stateProperty.set(GUIGeneState.NO_CHANGE_TARGET); + return; + } + } + } else if (event.getGestureSource() instanceof GUIOutput) { + Output source = ((GUIOutput) event.getGestureSource()).getGene(); + if (((GUIGene) event.getSource()).getGene() == source.getSource()) { + ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET); + } + } + stateProperty.set(GUIGeneState.TARGET); + } else { + stateProperty.set(GUIGeneState.FORBIDDEN_TARGET); + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + 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 + if (event.isPrimaryButtonDown()) { + if (event.getGestureSource() == event.getSource()) { + stateProperty.set(GUIGeneState.SOURCE); + } else { + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(GUIGeneState.HOVER); + // the user released the drag gesture on this node, react appropriately + if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) { + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).setChangingConnection(node); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, node); + } + } + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).updateLines(); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).updateLine(); + } + + } + }); + + + 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 (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.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 (stateProperty.get() == GUIGeneState.HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + getChildren().addAll(mainCircle, text); + getChildren().addAll(sockets); + getChildren().addAll(output, connectionNumber); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + switch (newValue) { + case ACTIVE_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + showLines(true); + setConnections(GUIGeneState.ACTIVE_HOVER); + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + showLines(true); + setConnections(GUIGeneState.INDIRECT_HOVER); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + showLines(false); + if (oldValue == GUIGeneState.ACTIVE_HOVER) { + setConnections(GUIGeneState.NEUTRAL); + } + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + for (int c = 0; c < lines.length; c++) { + final int i = c; + node.connections().get(c).addListener(new ChangeListener<Connection>() { + @Override + public void changed(ObservableValue<? extends Connection> observable, + Connection oldValue, Connection newValue) { + updateLine(i); + } + }); + } + + } + + + 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).getGene(), s = ((GUINode) source).getGene(); + if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= (int) GUI.resources.get("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."); + } + + + /** + * Place the end of the specified line on the output of the associated connection. + * + * @param index the line to be updated. + */ + 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 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS); + lines[index].setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + } else if (node.getConnection(index) instanceof Input) { + int inputIndex = ((Input) node.getConnection(index)).getIndex(); + lines[index].setEndX(2 * NODE_RADIUS); + lines[index].setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS); + } + } + + /** + * Updates the end of all lines to match the associated connections. + */ + public void updateLines() { + for (int c = 0; c < lines.length; c++) { + updateLine(c); + } + } + + /** + * Toggle visibility of all connection lines. + * + * @param value whether to show the lines or not. + */ + private void showLines(boolean value) { + for (int i = 0; i < lines.length; i++) { + lines[i].setVisible(value); + } + } + + @Override + public Node getGene() { + return node; + } + + + @Override + public void setConnections(GUIGeneState newState) { + for (int i = 0; i < lines.length; i++) { + if (node.getConnection(i) instanceof Node) { + parent.getGuiNode(((Node) node.getConnection(i)).getRow(), ((Node) node.getConnection(i)).getColumn()).setState(newState); + } else if (node.getConnection(i) instanceof Input) { + parent.getGuiInput(((Input) node.getConnection(i)).getIndex()).setState(newState); + } + } + } + + /** + * This method sets the connection currently being changed to the + * specified value. + */ + public void setChangingConnection(Connection newConnection) { + node.setConnection(connectionIndex, newConnection); + } + + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + +} diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java new file mode 100644 index 0000000..602a022 --- /dev/null +++ b/src/jcgp/gui/population/GUIOutput.java @@ -0,0 +1,277 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +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 javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.GUI; +import jcgp.CGP.Resources; +import jcgp.population.Connection; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUIOutput extends GUIGene { + + private Circle socket; + + private Label connectionLabel; + + private Line sourceLine; + + private Output output; + + public GUIOutput(ChromosomePane parentRef, final Output output, Resources resources, Line line) { + + this.parent = parentRef; + this.output = output; + this.sourceLine = line; + + relocate((((int) resources.get("columns") + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS, + (output.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + // set the line ends correctly + updateLine(); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text("O: " + output.getIndex()); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + socket = new Circle(-NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + socket.setId(String.valueOf(0)); + socket.setStroke(Paint.valueOf("black")); + + connectionLabel = new Label("S"); + connectionLabel.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); "); + connectionLabel.relocate(socket.getCenterX() + 5, socket.getCenterY() - 10); + connectionLabel.setVisible(false); + + /* + * Mouse event handlers on sockets + * + */ + socket.addEventFilter(MouseDragEvent.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(); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user is hovering over connection socket + connectionLabel.setVisible(true); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionLabel.setVisible(false); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + stateProperty.set(GUIGeneState.SOURCE); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + sourceLine.setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX()); + sourceLine.setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY()); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.isDragDetect()) { + // mouse was released before dragging out of the socket + updateLine(); + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + + + /* + * Mouse event handlers on whole gene + */ + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has entered this node, react appropriately + stateProperty.set(GUIGeneState.FORBIDDEN_TARGET); + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + 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 + if (event.isPrimaryButtonDown()) { + if (event.getGestureSource() == event.getSource()) { + stateProperty.set(GUIGeneState.SOURCE); + } else { + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // making a connection to an output is illegal + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(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 (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.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 (stateProperty.get() == GUIGeneState.HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + + getChildren().addAll(mainCircle, text, socket, connectionLabel); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + + switch (newValue) { + case ACTIVE_HOVER: + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + sourceLine.setVisible(true); + setConnections(GUIGeneState.ACTIVE_HOVER); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + sourceLine.setVisible(false); + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + output.sourceProperty().addListener(new ChangeListener<Connection>() { + @Override + public void changed(ObservableValue<? extends Connection> observable, + Connection oldValue, Connection newValue) { + updateLine(); + } + }); + + } + + public void updateLine() { + if (output.getSource() instanceof Node) { + int row = ((Node) output.getSource()).getRow(), + column = ((Node) output.getSource()).getColumn(); + sourceLine.setEndX(((column + 1) * (2 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS); + sourceLine.setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + } else if (output.getSource() instanceof Input) { + int inputIndex = ((Input) output.getSource()).getIndex(); + sourceLine.setEndX(2 * NODE_RADIUS); + sourceLine.setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS); + } + } + + @Override + public Output getGene() { + return output; + } + + @Override + public void setConnections(GUIGeneState newState) { + if (output.getSource() instanceof Node) { + parent.getGuiNode(((Node) output.getSource()).getRow(), ((Node) output.getSource()).getColumn()).setState(newState); + } else if (output.getSource() instanceof Input) { + parent.getGuiInput(((Input) output.getSource()).getIndex()).setState(newState); + } + } + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } +} |