You can use some of the in-built Java functions to help accomplish your task.
For example CSS PsuedoClasses and Toggles managed by a ToggleGroup.
It isn’t necessary to do it this way, the solution you have which does not use these other JavaFX features, is just fine. It is just kind of neat to use some of the standard JavaFX functions.
LightApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class LightApp extends Application {
@Override
public void start(final Stage stage) throws Exception {
final Bulb[] bulbs = {
new Bulb(),
new Bulb(),
new Bulb()
};
Scene scene = new Scene(new LightArray(bulbs));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
LightArray.java
import javafx.geometry.Insets;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
public class LightArray extends HBox {
public LightArray(Bulb... bulbs) {
super(10, bulbs);
setPadding(new Insets(10));
ToggleGroup toggleGroup = new ToggleGroup();
for (Bulb bulb: bulbs) {
bulb.setToggleGroup(toggleGroup);
}
setOnMouseClicked(event -> {
if (event.getTarget() instanceof Bulb) {
toggleGroup.selectToggle((Bulb) event.getTarget());
} else {
toggleGroup.selectToggle(null);
}
});
getStylesheets().add(
this.getClass().getResource("bulb.css").toExternalForm()
);
}
}
Bulb.java
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.scene.control.*;
import javafx.scene.shape.Circle;
class Bulb extends Circle implements Toggle {
private ObjectProperty<ToggleGroup> toggleGroup = new SimpleObjectProperty<>();
Bulb() {
super(30);
getStyleClass().add("bulb");
}
@Override
public void setSelected(boolean selected) {
this.selected.set(selected);
}
@Override
public boolean isSelected() {
return selected.get();
}
@Override
public BooleanProperty selectedProperty() {
return selected;
}
public BooleanProperty selected =
new BooleanPropertyBase(false) {
@Override protected void invalidated() {
pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
}
@Override public Object getBean() {
return Bulb.this;
}
@Override public String getName() {
return "on";
}
};
private static final PseudoClass
ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
@Override
public ToggleGroup getToggleGroup() {
return toggleGroup.get();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
this.toggleGroup.set(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroup;
}
}
bulb.css
.bulb {
-fx-fill: lightslategray;
}
.bulb:on {
-fx-fill: gold;
}
An additional common thing which is often done with JavaFX (and I haven’t done here), is to make items which can be styled by CSS (e.g. Regions or Panes) and then apply the styling to them. For example, instead of the Bulb extending Circle, it could extend StackPane and then the bulb shape could be customized in css via multiple layered backgrounds and svg shapes (this is how other similar things such as radio buttons are implemented).