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.

344 lines
13 KiB

package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.model.Leg;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.model.*;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller used to display a running race.
*/
public class RaceViewController extends Controller2 {
private VisualiserRaceEvent visualiserRace;
private VisualiserRaceState raceState;
private ControllerClient controllerClient;
private ResizableRaceCanvas raceCanvas;
private KeyFactory keyFactory = new KeyFactory();
private boolean infoTableShow = true; // shown or hidden
private boolean isHost;
// note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController;
private @FXML GridPane canvasBase;
private @FXML SplitPane race;
private @FXML StackPane arrowPane;
private @FXML Label timer;
private @FXML Label FPS;
private @FXML Label timeZone;
private @FXML CheckBox showFPS;
private @FXML TableView<VisualiserBoat> boatInfoTable;
private @FXML TableColumn<VisualiserBoat, String> boatPlacingColumn;
private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
private @FXML LineChart<Number, Number> sparklineChart;
private @FXML AnchorPane annotationPane;
/**
* Displays a specified race.
* Intended to be called on loading the scene.
* @param visualiserRace Object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost is user a host
*/
public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
this.visualiserRace = visualiserRace;
this.raceState = visualiserRace.getVisualiserRaceState();
this.controllerClient = controllerClient;
this.isHost = isHost;
keyFactory.load();
initKeypressHandler();
initialiseRaceVisuals();
}
/**
* Sets up the listener and actions that occur when a key is pressed.
*/
public void initKeypressHandler() {
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
// tab key
if (codeString.equals("TAB")){toggleTable();}
// any key pressed
ControlKey controlKey = keyFactory.getKey(codeString);
if(controlKey != null) {
try {
controlKey.onAction(); // Change key state if applicable
controllerClient.sendKey(controlKey);
event.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Logger.getGlobal().log(Level.WARNING, "RaceViewController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
}
}
// escape key
if(event.getCode() == KeyCode.ESCAPE) {
try {
if (isHost) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Exit Race");
alert.setContentText("Do you wish to quit the race? You are the host");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
App.game.endEvent();
loadTitleScreen();
}
} else {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Exit Race");
alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
loadTitleScreen();
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
private void initialiseRaceVisuals() {
// initialise displays
initialiseFps();
initialiseInfoTable();
initialiseRaceCanvas();
initialiseView3D();
initialiseRaceClock();
raceTimer(); // start the timer
new Annotations(this.annotationPane, this.raceCanvas);
new Sparkline(this.raceState, this.sparklineChart);
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
arrowController.setWindProperty(this.raceState.windProperty());
}
private void initialiseView3D() {
List<VisualiserBoat> boats = raceState.getBoats();
for(VisualiserBoat boat: boats) {
boat.positionProperty().addListener((o, prev, curr) -> {
System.out.println(boat.getCountry() + " is at " + curr.toString());
});
}
}
/**
* Initialises the frame rate functionality. This allows for toggling the
* frame rate, and connect the fps label to the race's fps property.
*/
private void initialiseFps() {
// fps toggle listener
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
// fps label display
this.visualiserRace.getFrameRateProperty().addListener((observable,
oldValue, newValue) -> {
Platform.runLater(() ->
this.FPS.setText("FPS: " + newValue.toString()));
});
}
/**
* Initialises the information table view to listen to a given race.
*/
public void initialiseInfoTable() {
// list of boats to display data for
ObservableList<VisualiserBoat> boats = FXCollections
.observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats());
SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats);
sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty());
// update list when boat information changes
this.visualiserRace.getVisualiserRaceState().getBoats().addListener(
(ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> {
boats.setAll(this.visualiserRace.getVisualiserRaceState().getBoats());
}));
// set table data
boatInfoTable.setItems(sortedBoats);
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty());
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty());
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty());
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty());
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
@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()));
}
}
};
}
});
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory(
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
@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());
}
}
};
}
});
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to
* read data from.
*/
private void initialiseRaceCanvas() {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(raceState);
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
// draw and display
raceCanvas.draw();
raceCanvas.setVisible(true);
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Initialises the race clock to listen to the specified race.
*/
private void initialiseRaceClock() {
raceState.getRaceClock().durationProperty().addListener((observable,
oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Transition from the race view to the finish view.
*/
public void finishRace() throws IOException {
RaceFinishController fc =
(RaceFinishController)loadScene("raceFinish.fxml");
fc.loadFinish(raceState.getBoats());
}
/**
* Timer which monitors the race.
*/
private void raceTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//If the race has finished, go to finish view.
if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
stop(); // stop the timer
try {
finishRace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// refresh visual race display
raceCanvas.drawRace();
boatInfoTable.sort();
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
try {
loadTitleScreen();
} catch (Exception e) {
e.printStackTrace();
}
//TODO we should display an error to the user
//TODO also need to "reset" any state (race, connections, etc...).
}
}
}.start();
}
/**
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
if (infoTableShow) {
race.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
} else {
race.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
arrowPane.setTranslateX(0);
arrowPane.setTranslateY(0);
}
boatInfoTable.refresh();
infoTableShow = !infoTableShow;
}
}