package jcgp.gui.population; import javafx.geometry.VPos; import javafx.scene.Group; 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.constants.Constants; /** * Defines the general behaviour of the visual representation of each chromosome gene. *

* In practice, this is subclass of {@code javafx.scene.Group} containing a {@code Circle} * object and a {@code Text} object. Subclasses may add further elements to the group, for * instance to display connection input and output sockets. *

* Genes also contain a locked property. When locked, some gene states behave slightly * differently. This is used so genes remain highlighted even in the neutral state. The * gene lock is in fact recursive; a gene can be locked multiple times and only unlocking * it as many times will actually revert it back to its unlocked state. This allows multiple * pathways to lock the same gene independently without affecting each other; the gene remains * locked until no pathways are locking it. * * @author Eduardo Pedroni * */ public abstract class GUIGene extends Group { /** * This {@code enum} type defines a finite list of all states * a gene can take. Each state represents a particular steady * situation, and has its own GUI appearance associated with it: * a combination of connection line visibility, gene background colour * and other visual characteristics. * * @author Eduardo Pedroni * */ public enum GUIGeneState { /** * No user interaction at all. */ NEUTRAL, /** * User is simply hovering over the node. */ HOVER, /** * User is hovering over a node connected to this one. */ EXTENDED_HOVER, /** * User is hovering over an output connected to this gene. */ ACTIVE_HOVER, GOOD_TARGET, NEUTRAL_TARGET, BAD_TARGET } private GUIGeneState currentState = GUIGeneState.NEUTRAL; private Text text; private Circle mainCircle; /** * Recursive lock; lock == 0 means unlocked, lock > 0 means locked. * Accessing using {@code setLock(...)}. */ private int lock = 0; /** * Initialises the {@code Text} and {@code Circle} objects so that all genes are standardised. */ protected GUIGene() { text = new Text(); text.setFont(Font.font("Arial", 12)); text.setTextOrigin(VPos.CENTER); text.setTextAlignment(TextAlignment.CENTER); text.setWrappingWidth(Constants.NODE_RADIUS * 2); text.setX(-Constants.NODE_RADIUS); mainCircle = new Circle(Constants.NODE_RADIUS, Constants.NEUTRAL_PAINT); mainCircle.setStroke(Paint.valueOf("black")); getChildren().addAll(mainCircle, text); } /** * Sets the gene's text field. * * @param newText the text string to be displayed. */ public void setText(String newText) { text.setText(newText); } /** * @return the gene's current state. */ public GUIGeneState getState() { return currentState; } /** * Gene states are standardised: all gene subclasses behave the same way in each state. *
* This design choice was made for the sake of consistency. Rather than controlling the * appearance of the genes with logic in the state transition method AND the mouse handlers, * the states are now consistent across all types of gene. The mouse handlers implement * whatever logic is necessary to determine the gene's new state given a certain user input, * but the states themselves are the same for all genes. *
* The transition logic for each type of gene is defined in its respective handler class: * {@code InputHandlers}, {@code NodeHandlers} and {@code OutputHandlers}. * * @param newState the gene's new state. */ public final void setState(GUIGeneState newState) { switch (newState) { case NEUTRAL: mainCircle.setFill(isLocked() ? Constants.HARD_HIGHLIGHT_PAINT : Constants.NEUTRAL_PAINT); setLinesVisible(isLocked()); break; case HOVER: mainCircle.setFill(Constants.MEDIUM_HIGHLIGHT_PAINT); setLinesVisible(true); break; case EXTENDED_HOVER: mainCircle.setFill(Constants.SOFT_HIGHLIGHT_PAINT); setLinesVisible(isLocked()); break; case ACTIVE_HOVER: mainCircle.setFill(Constants.SOFT_HIGHLIGHT_PAINT); setLinesVisible(true); break; case GOOD_TARGET: mainCircle.setFill(Constants.GOOD_SELECTION_PAINT); break; case NEUTRAL_TARGET: mainCircle.setFill(Constants.NEUTRAL_SELECTION_PAINT); break; case BAD_TARGET: mainCircle.setFill(Constants.BAD_SELECTION_PAINT); break; } currentState = newState; } /** * For the sake of practicality, all {@code GUIGene} instances must implement this * method. It sets the visibility of all of the gene's lines, if it has any. * * @param value the visibility value. */ protected abstract void setLinesVisible(boolean value); /** * @return true if the gene is locked, false otherwise. */ public boolean isLocked() { return lock > 0; } /** * Locks or unlocks the gene once. Locked genes * behave slightly differently in some states. *
* Unlocking an already unlocked gene does nothing. * * @param value true to lock, false to unlock; */ public void setLock(boolean value) { if (value) { lock++; } else if (lock > 0) { lock--; } else { lock = 0; } } }