package visualiser.Controllers; import javafx.application.Platform; import javafx.beans.property.Property; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.shape.Circle; import shared.model.Bearing; import shared.model.Wind; import visualiser.model.VisualiserRace; /** * Controller for the arrow.fxml view. */ public class ArrowController { @FXML private Pane compass; @FXML private StackPane arrowStackPane; @FXML private ImageView arrowImage; @FXML private Circle circle; @FXML private Label northLabel; @FXML private Label windLabel; @FXML private Label speedLabel; /** * This is the property our arrow control binds to. */ private Property wind; /** * Constructor. */ public ArrowController() { } /** * Sets which wind property the arrow control should bind to. * @param wind The wind property to bind to. */ public void setWindProperty(Property wind) { this.wind = wind; wind.addListener((observable, oldValue, newValue) -> { if (newValue != null) { Platform.runLater(() -> updateWind(newValue)); } }); } /** * Updates the control to use the new wind value. * This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed). * @param wind The wind value to use. */ private void updateWind(Wind wind) { updateWindBearing(wind.getWindDirection()); updateWindSpeed(wind.getWindSpeed()); } /** * Updates the control to account for the new wind speed. * This changes the length (height) of the wind arrow, and updates the speed label. * @param speedKnots The new wind speed, in knots. */ private void updateWindSpeed(double speedKnots) { updateWindArrowLength(speedKnots); updateWindSpeedLabel(speedKnots); } /** * Updates the length of the wind arrow according to the specified wind speed. * @param speedKnots Wind speed, in knots. */ private void updateWindArrowLength(double speedKnots) { //At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height. double minKnots = 2; double maxKnots = 30; double deltaKnots = maxKnots - minKnots; double minHeight = 25; double maxHeight = 75; double deltaHeight = maxHeight - minHeight; //Clamp speed. if (speedKnots > maxKnots) { speedKnots = maxKnots; } else if (speedKnots < minKnots) { speedKnots = minKnots; } //How far between the knots bounds is the current speed? double currentDeltaKnots = speedKnots - minKnots; double currentKnotsScalar = currentDeltaKnots / deltaKnots; //Thus, how far between the pixel height bounds should the arrow height be? double newHeight = minHeight + (currentKnotsScalar * deltaHeight); arrowImage.setFitHeight(newHeight); } /** * Updates the wind speed label according to the specified wind speed. * @param speedKnots Wind speed, in knots. */ private void updateWindSpeedLabel(double speedKnots) { speedLabel.setText(String.format("%.1fkn", speedKnots)); } /** * Updates the control to account for a new wind bearing. * This rotates the arrow according to the bearing. * @param bearing The bearing to use to rotate arrow. */ private void updateWindBearing(Bearing bearing) { //We need to display wind-from, so add 180 degrees. Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d); //Rotate the wind arrow. arrowStackPane.setRotate(fromBearing.degrees()); } }