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;
}
}
}