You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
13 KiB
437 lines
13 KiB
package visualiser.Controllers;
|
|
|
|
|
|
import javafx.animation.AnimationTimer;
|
|
import javafx.application.Platform;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.collections.ObservableList;
|
|
import javafx.fxml.FXML;
|
|
import javafx.scene.chart.LineChart;
|
|
import javafx.scene.control.*;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.input.KeyEvent;
|
|
import javafx.scene.layout.AnchorPane;
|
|
import javafx.scene.layout.GridPane;
|
|
import javafx.scene.layout.Pane;
|
|
import javafx.scene.layout.StackPane;
|
|
import javafx.util.Callback;
|
|
import network.Messages.Enums.RaceStatusEnum;
|
|
import shared.model.Leg;
|
|
import visualiser.gameController.ControllerClient;
|
|
import visualiser.gameController.Keys.ControlKey;
|
|
import visualiser.gameController.Keys.KeyFactory;
|
|
import visualiser.model.*;
|
|
import visualiser.network.ServerConnection;
|
|
|
|
import java.net.URL;
|
|
import java.util.ResourceBundle;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Controller used to display a running race.
|
|
*/
|
|
public class RaceController extends Controller {
|
|
|
|
|
|
/**
|
|
* The race object which describes the currently occurring race.
|
|
*/
|
|
private VisualiserRace visualiserRace;
|
|
|
|
/**
|
|
* An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats.
|
|
*/
|
|
private ObservableList<VisualiserBoat> tableBoatList;
|
|
|
|
/**
|
|
* The canvas that draws the race.
|
|
*/
|
|
private ResizableRaceCanvas raceCanvas;
|
|
|
|
/**
|
|
* The sparkline graph.
|
|
*/
|
|
private Sparkline sparkline;
|
|
|
|
/**
|
|
* The arrow controller.
|
|
*/
|
|
@FXML private ArrowController arrowController;
|
|
|
|
/**
|
|
* Service for sending keystrokes to server
|
|
*/
|
|
private ControllerClient controllerClient;
|
|
|
|
/**
|
|
* The connection to the server.
|
|
*/
|
|
private ServerConnection serverConnection;
|
|
|
|
|
|
@FXML private GridPane canvasBase;
|
|
|
|
|
|
@FXML private SplitPane race;
|
|
|
|
/**
|
|
* This is the root node of the arrow control.
|
|
*/
|
|
@FXML private Pane arrow;
|
|
|
|
/**
|
|
* This is the pane we place the actual arrow control inside of.
|
|
*/
|
|
@FXML private StackPane arrowPane;
|
|
@FXML private Label timer;
|
|
@FXML private Label FPS;
|
|
@FXML private Label timeZone;
|
|
@FXML private CheckBox showFPS;
|
|
@FXML private TableView<VisualiserBoat> boatInfoTable;
|
|
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
|
|
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
|
|
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
|
|
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
|
|
@FXML private LineChart<Number, Number> sparklineChart;
|
|
@FXML private AnchorPane annotationPane;
|
|
|
|
|
|
|
|
/**
|
|
* Ctor.
|
|
*/
|
|
public RaceController() {
|
|
}
|
|
|
|
@Override
|
|
public void initialize(URL location, ResourceBundle resources) {
|
|
KeyFactory keyFactory = KeyFactory.getFactory();
|
|
|
|
// Initialise keyboard handler
|
|
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
|
String codeString = event.getCode().toString();
|
|
ControlKey controlKey = keyFactory.getKey(codeString);
|
|
if(controlKey != null) {
|
|
try {
|
|
controllerClient.sendKey(controlKey);
|
|
controlKey.onAction(); // Change key state if applicable
|
|
event.consume();
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialises the various UI components to listen to the {@link #visualiserRace}.
|
|
*/
|
|
private void initialiseRace() {
|
|
//Fps display.
|
|
initialiseFps(this.visualiserRace);
|
|
|
|
//Information table.
|
|
initialiseInfoTable(this.visualiserRace);
|
|
|
|
//Sparkline.
|
|
initialiseSparkline(this.visualiserRace);
|
|
|
|
//Arrow.
|
|
initialiseArrow(this.visualiserRace);
|
|
|
|
//Race canvas.
|
|
initialiseRaceCanvas(this.visualiserRace);
|
|
|
|
//Race timezone label.
|
|
initialiseRaceTimezoneLabel(this.visualiserRace);
|
|
|
|
//Race clock.
|
|
initialiseRaceClock(this.visualiserRace);
|
|
|
|
|
|
//Start the race animation timer.
|
|
raceTimer();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property.
|
|
* @param visualiserRace The race to connect the fps label to.
|
|
*/
|
|
private void initialiseFps(VisualiserRace visualiserRace) {
|
|
|
|
//On/off toggle.
|
|
initialiseFpsToggle();
|
|
|
|
//Label value.
|
|
initialiseFpsLabel(visualiserRace);
|
|
}
|
|
|
|
/**
|
|
* Initialises a listener for the fps toggle.
|
|
*/
|
|
private void initialiseFpsToggle() {
|
|
|
|
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
|
|
if (showFPS.isSelected()) {
|
|
FPS.setVisible(true);
|
|
|
|
} else {
|
|
FPS.setVisible(false);
|
|
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Initialises the fps label to update when the race fps changes.
|
|
* @param visualiserRace The race to monitor the frame rate of.
|
|
*/
|
|
private void initialiseFpsLabel(VisualiserRace visualiserRace) {
|
|
|
|
visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> {
|
|
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initialises the information table view to listen to a given race.
|
|
* @param race Race to listen to.
|
|
*/
|
|
public void initialiseInfoTable(VisualiserRace race) {
|
|
|
|
//Copy list of boats.
|
|
this.tableBoatList = FXCollections.observableArrayList(race.getBoats());
|
|
|
|
|
|
//Set up table.
|
|
boatInfoTable.setItems(this.tableBoatList);
|
|
|
|
|
|
//Set up each column.
|
|
|
|
//Name.
|
|
boatTeamColumn.setCellValueFactory(
|
|
cellData -> cellData.getValue().nameProperty() );
|
|
|
|
//Speed.
|
|
boatSpeedColumn.setCellValueFactory(
|
|
cellData -> cellData.getValue().currentSpeedProperty() );
|
|
|
|
//Kind of ugly, but allows for formatting an observed speed.
|
|
boatSpeedColumn.setCellFactory(
|
|
//Callback object.
|
|
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
|
|
|
|
//Callback function.
|
|
@Override
|
|
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
|
|
//We return a table cell that populates itself with a Number, and formats it.
|
|
return new TableCell<VisualiserBoat, Number>(){
|
|
|
|
//Function to update the cell text.
|
|
@Override
|
|
protected void updateItem(Number item, boolean empty) {
|
|
|
|
if (item != null) {
|
|
super.updateItem(item, empty);
|
|
|
|
setText(String.format("%.2fkn", item.doubleValue()));
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
//Last mark.
|
|
boatMarkColumn.setCellValueFactory(
|
|
cellData -> cellData.getValue().legProperty() );
|
|
|
|
//Kind of ugly, but allows for turning an observed Leg into a string.
|
|
boatMarkColumn.setCellFactory(
|
|
//Callback object.
|
|
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
|
|
|
|
//Callback function.
|
|
@Override
|
|
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
|
|
//We return a table cell that populates itself with a Leg's name.
|
|
return new TableCell<VisualiserBoat, Leg>(){
|
|
|
|
//Function to update the cell text.
|
|
@Override
|
|
protected void updateItem(Leg item, boolean empty) {
|
|
|
|
if (item != null) {
|
|
super.updateItem(item, empty);
|
|
|
|
setText(item.getName());
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
//Current place within race.
|
|
boatPlacingColumn.setCellValueFactory(
|
|
cellData -> cellData.getValue().positionProperty() );
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}.
|
|
* @param race The race to listen to.
|
|
*/
|
|
private void initialiseSparkline(VisualiserRace race) {
|
|
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
|
|
this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart);
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
|
|
* @param race Race to read data from.
|
|
*/
|
|
private void initialiseRaceCanvas(VisualiserRace race) {
|
|
|
|
//Create canvas.
|
|
raceCanvas = new ResizableRaceCanvas(race);
|
|
|
|
//Set properties.
|
|
raceCanvas.setMouseTransparent(true);
|
|
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
|
|
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
|
|
|
|
//Draw it and show it.
|
|
raceCanvas.draw();
|
|
raceCanvas.setVisible(true);
|
|
|
|
//Add to scene.
|
|
canvasBase.getChildren().add(0, raceCanvas);
|
|
}
|
|
|
|
|
|
/**
|
|
* Intialises the race time zone label with the race's time zone.
|
|
* @param race The race to get time zone from.
|
|
*/
|
|
private void initialiseRaceTimezoneLabel(VisualiserRace race) {
|
|
timeZone.setText(race.getRaceClock().getTimeZone());
|
|
}
|
|
|
|
/**
|
|
* Initialises the race clock to listen to the specified race.
|
|
* @param race The race to listen to.
|
|
*/
|
|
private void initialiseRaceClock(VisualiserRace race) {
|
|
|
|
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
|
|
race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
|
|
Platform.runLater(() -> {
|
|
timer.setText(newValue);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Displays a specified race.
|
|
* @param visualiserRace Object modelling the race.
|
|
* @param controllerClient Socket Client that manipulates the controller.
|
|
* @param serverConnection The connection to the server.
|
|
*/
|
|
public void startRace(VisualiserRace visualiserRace, ControllerClient controllerClient, ServerConnection serverConnection) {
|
|
|
|
this.visualiserRace = visualiserRace;
|
|
this.controllerClient = controllerClient;
|
|
this.serverConnection = serverConnection;
|
|
|
|
initialiseRace();
|
|
|
|
//Display this controller.
|
|
race.setVisible(true);
|
|
|
|
|
|
// set up annotation displays
|
|
new Annotations(annotationPane, raceCanvas);
|
|
}
|
|
|
|
/**
|
|
* Transition from the race view to the finish view.
|
|
* @param boats boats there are in the race.
|
|
*/
|
|
public void finishRace(ObservableList<VisualiserBoat> boats){
|
|
race.setVisible(false);
|
|
parent.enterFinish(boats);
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialises the arrow controller with data from the race to observe.
|
|
* @param race The race to observe.
|
|
*/
|
|
private void initialiseArrow(VisualiserRace race) {
|
|
arrowController.setWindProperty(race.windProperty());
|
|
}
|
|
|
|
|
|
/**
|
|
* Timer which monitors the race.
|
|
*/
|
|
private void raceTimer() {
|
|
new AnimationTimer() {
|
|
@Override
|
|
public void handle(long arg0) {
|
|
|
|
//Get the current race status.
|
|
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
|
|
|
|
|
|
//If the race has finished, go to finish view.
|
|
if (raceStatus == RaceStatusEnum.FINISHED) {
|
|
//Stop this timer.
|
|
stop();
|
|
|
|
//Hide this, and display the finish controller.
|
|
finishRace(visualiserRace.getBoats());
|
|
|
|
} else {
|
|
//Otherwise, render the canvas.
|
|
raceCanvas.drawRace();
|
|
|
|
|
|
//Sort the tableview. Doesn't automatically work for all columns.
|
|
boatInfoTable.sort();
|
|
|
|
}
|
|
|
|
//Return to main screen if we lose connection.
|
|
if (!serverConnection.isAlive()) {
|
|
race.setVisible(false);
|
|
parent.enterTitle();
|
|
//TODO currently this doesn't work correctly - the title screen remains visible after clicking join game
|
|
//TODO we should display an error to the user
|
|
//TODO also need to "reset" any state (race, connections, etc...).
|
|
}
|
|
|
|
}
|
|
}.start();
|
|
}
|
|
|
|
}
|