diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 5943625d..fe603783 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,12 +1,35 @@ package seng302.gameServer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; import javafx.scene.paint.Color; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.InputSource; -import seng302.gameServer.messages.*; -import seng302.model.*; +import seng302.gameServer.messages.BoatAction; +import seng302.gameServer.messages.BoatStatus; +import seng302.gameServer.messages.ChatterMessage; +import seng302.gameServer.messages.CustomizeRequestType; +import seng302.gameServer.messages.MarkRoundingMessage; +import seng302.gameServer.messages.MarkType; +import seng302.gameServer.messages.Message; +import seng302.gameServer.messages.RoundingBoatStatus; +import seng302.gameServer.messages.YachtEventCodeMessage; +import seng302.gameServer.messages.YachtEventType; +import seng302.model.GeoPoint; +import seng302.model.Limit; +import seng302.model.Player; +import seng302.model.PolarTable; +import seng302.model.ServerYacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; @@ -14,10 +37,6 @@ import seng302.model.token.Token; import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; import seng302.utilities.XMLParser; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.util.*; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; /** @@ -341,6 +360,12 @@ public class GameState implements Runnable { case DOWNWIND: playerYacht.turnDownwind(); break; + case CONTINUOUSLY_TURNING: + playerYacht.setContinuouslyTurning(true); + break; + case DEFAULT_TURNING: + playerYacht.setContinuouslyTurning(false); + break; } } diff --git a/src/main/java/seng302/gameServer/messages/BoatAction.java b/src/main/java/seng302/gameServer/messages/BoatAction.java index 9003958a..9bd2131f 100644 --- a/src/main/java/seng302/gameServer/messages/BoatAction.java +++ b/src/main/java/seng302/gameServer/messages/BoatAction.java @@ -14,7 +14,9 @@ public enum BoatAction { TACK_GYBE(4), UPWIND(5), DOWNWIND(6), - MAINTAIN_HEADING(7); + MAINTAIN_HEADING(7), + CONTINUOUSLY_TURNING(8), + DEFAULT_TURNING(9); private final int type; private static final Map intToTypeMap = new HashMap<>(); diff --git a/src/main/java/seng302/model/GameKeyBind.java b/src/main/java/seng302/model/GameKeyBind.java new file mode 100644 index 00000000..1c765adc --- /dev/null +++ b/src/main/java/seng302/model/GameKeyBind.java @@ -0,0 +1,78 @@ +package seng302.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import javafx.scene.input.KeyCode; + +public class GameKeyBind { + + private static GameKeyBind instance; + private Map keyToActionMap; + private Map actionToKeyMap; + private Boolean continuouslyTurning; + + + private GameKeyBind() { + setToDefault(); + } + + public void setToDefault() { + actionToKeyMap = new HashMap<>(); + keyToActionMap = new HashMap<>(); + continuouslyTurning = false; + // default key bindings + ArrayList keys = new ArrayList<>(); + keys.add(KeyCode.Z); + keys.add(KeyCode.X); + keys.add(KeyCode.SPACE); + keys.add(KeyCode.SHIFT); + keys.add(KeyCode.ENTER); + keys.add(KeyCode.PAGE_UP); + keys.add(KeyCode.PAGE_DOWN); + for (int i = 0; i < 7; i++) { + actionToKeyMap.put(KeyAction.getType(i + 1), keys.get(i)); + keyToActionMap.put(keys.get(i), KeyAction.getType(i + 1)); + } + } + + public static GameKeyBind getInstance() { + if (instance == null) { + instance = new GameKeyBind(); + } + return instance; + } + + public KeyCode getKeyCode(KeyAction keyAction) { + return instance.actionToKeyMap.get(keyAction); + } + + /** + * Binds a key to a key action + * + * @return true if successfully bind + */ + public boolean bindKeyToAction(KeyCode keyCode, KeyAction keyAction) { + if (instance.keyToActionMap.containsKey(keyCode)) { + // if the key has been bound to other action, return false + return false; + } else { + instance.keyToActionMap.put(keyCode, keyAction); // add key -> action + KeyCode oldKeyCode = instance.actionToKeyMap + .get(keyAction); // get old key for the action + instance.keyToActionMap.remove(oldKeyCode); // remove the old key -> action + instance.actionToKeyMap + .replace(keyAction, keyCode); // replace the old key by the newer one + return true; + } + } + + public void toggleTurningMode() { + continuouslyTurning = !continuouslyTurning; + } + + public Boolean isContinuouslyTurning() { + return continuouslyTurning; + } + +} diff --git a/src/main/java/seng302/model/KeyAction.java b/src/main/java/seng302/model/KeyAction.java new file mode 100644 index 00000000..1b8c2fa1 --- /dev/null +++ b/src/main/java/seng302/model/KeyAction.java @@ -0,0 +1,35 @@ +package seng302.model; + +import java.util.HashMap; +import java.util.Map; + +public enum KeyAction { + ZOOM_IN(1), + ZOOM_OUT(2), + VMG(3), + SAILS_STATE(4), + TACK_GYBE(5), + UPWIND(6), + DOWNWIND(7); + + private final int type; + private static final Map intToTypeMap = new HashMap<>(); + + static { + for (KeyAction type : KeyAction.values()) { + intToTypeMap.put(type.getValue(), type); + } + } + + KeyAction(int type) { + this.type = type; + } + + public static KeyAction getType(int value) { + return intToTypeMap.get(value); + } + + public int getValue() { + return this.type; + } +} diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index ee692d63..7dccdc09 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -1,5 +1,6 @@ package seng302.model; +import java.util.HashMap; import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,8 +11,6 @@ import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; -import java.util.HashMap; - /** * Yacht class for the racing boat.

Class created to store more variables (eg. boat statuses) * compared to the XMLParser boat class, also done outside Boat class because some old variables are @@ -59,6 +58,8 @@ public class ServerYacht { private Integer powerUpSpeedMultiplier; private Integer powerUpHandlingMultiplier; + //turning mode + private Boolean continuouslyTurning; public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName, String boatName, String country) { @@ -81,10 +82,10 @@ public class ServerYacht { this.powerUp = null; this.powerUpSpeedMultiplier = 1; this.powerUpHandlingMultiplier = 1; - this.hasEnteredRoundingZone = false; this.hasPassedLine = false; this.hasPassedThroughGate = false; + this.continuouslyTurning = false; } @@ -194,44 +195,52 @@ public class ServerYacht { public void turnUpwind() { disableAutoPilot(); Double normalizedHeading = normalizeHeading(); - if (normalizedHeading == 0) { - if (lastHeading < 180) { - adjustHeading(-turnStep); - } else { - adjustHeading(turnStep); - } - } else if (normalizedHeading == 180) { - if (lastHeading < 180) { - adjustHeading(turnStep); - } else { - adjustHeading(-turnStep); - } - } else if (normalizedHeading < 180) { - adjustHeading(-turnStep); - } else { + if (continuouslyTurning) { adjustHeading(turnStep); + } else { + if (normalizedHeading == 0) { + if (lastHeading < 180) { + adjustHeading(-turnStep); + } else { + adjustHeading(turnStep); + } + } else if (normalizedHeading == 180) { + if (lastHeading < 180) { + adjustHeading(turnStep); + } else { + adjustHeading(-turnStep); + } + } else if (normalizedHeading < 180) { + adjustHeading(-turnStep); + } else { + adjustHeading(turnStep); + } } } public void turnDownwind() { disableAutoPilot(); Double normalizedHeading = normalizeHeading(); - if (normalizedHeading == 0) { - if (lastHeading < 180) { - adjustHeading(turnStep); - } else { - adjustHeading(-turnStep); - } - } else if (normalizedHeading == 180) { - if (lastHeading < 180) { - adjustHeading(-turnStep); - } else { - adjustHeading(turnStep); - } - } else if (normalizedHeading < 180) { - adjustHeading(turnStep); - } else { + if (continuouslyTurning) { adjustHeading(-turnStep); + } else { + if (normalizedHeading == 0) { + if (lastHeading < 180) { + adjustHeading(turnStep); + } else { + adjustHeading(-turnStep); + } + } else if (normalizedHeading == 180) { + if (lastHeading < 180) { + adjustHeading(-turnStep); + } else { + adjustHeading(turnStep); + } + } else if (normalizedHeading < 180) { + adjustHeading(turnStep); + } else { + adjustHeading(-turnStep); + } } } @@ -448,6 +457,10 @@ public class ServerYacht { return boatType; } + public void setContinuouslyTurning(Boolean continuouslyTurning) { + this.continuouslyTurning = continuouslyTurning; + } + public Integer getPowerUpSpeedMultiplier() { return powerUpSpeedMultiplier; } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 061b593a..e964914c 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -31,6 +31,8 @@ import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatStatus; import seng302.gameServer.messages.YachtEventType; import seng302.model.ClientYacht; +import seng302.model.GameKeyBind; +import seng302.model.KeyAction; import seng302.model.RaceState; import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.parser.MarkRoundingData; @@ -70,6 +72,8 @@ public class GameClient { private ArrayList finishedBoats = new ArrayList<>(); + private GameKeyBind gameKeyBind; // all the key binding setting. + private ObservableList clientLobbyList = FXCollections.observableArrayList(); /** @@ -79,6 +83,7 @@ public class GameClient { */ public GameClient(Pane holder) { this.holderPane = holder; + this.gameKeyBind = GameKeyBind.getInstance(); } /** @@ -372,16 +377,16 @@ public class GameClient { } return; } - switch (e.getCode()) { - case SPACE: // align with vmg - socketThread.sendBoatAction(BoatAction.VMG); break; - case PAGE_UP: // upwind - socketThread.sendBoatAction(BoatAction.UPWIND); break; - case PAGE_DOWN: // downwind - socketThread.sendBoatAction(BoatAction.DOWNWIND); break; - case ENTER: // tack/gybe - // if chat box is active take whatever is in there and send it to server - socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; + + if (gameKeyBind.getKeyCode(KeyAction.VMG) == e.getCode()) { // align with vmg + socketThread.sendBoatAction(BoatAction.VMG); + } else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()) { // upwind + socketThread.sendBoatAction(BoatAction.UPWIND); + } else if (gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) { // downwind + socketThread.sendBoatAction(BoatAction.DOWNWIND); + } else if (gameKeyBind.getKeyCode(KeyAction.TACK_GYBE) == e.getCode()) { // tack/gybe + // if chat box is active take whatever is in there and send it to server + socketThread.sendBoatAction(BoatAction.TACK_GYBE); } } @@ -390,15 +395,13 @@ public class GameClient { if (raceView.isChatInputFocused()) { return; } - switch (e.getCode()) { - //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) - case SHIFT: // sails in/sails out - socketThread.sendBoatAction(BoatAction.SAILS_IN); - allBoatsMap.get(socketThread.getClientId()).toggleSail(); - break; - case PAGE_UP: - case PAGE_DOWN: - socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; + + if (gameKeyBind.getKeyCode(KeyAction.SAILS_STATE) == e.getCode()) { // sails in/sails out + socketThread.sendBoatAction(BoatAction.SAILS_IN); + allBoatsMap.get(socketThread.getClientId()).toggleSail(); + } else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode() + || gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) { + socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); } } @@ -500,4 +503,14 @@ public class GameClient { public Map getAllBoatsMap() { return allBoatsMap; } + + public void sendToggleTurningModePacket() { + if (socketThread != null) { + if (gameKeyBind.isContinuouslyTurning()) { + socketThread.sendBoatAction(BoatAction.CONTINUOUSLY_TURNING); + } else { + socketThread.sendBoatAction(BoatAction.DEFAULT_TURNING); + } + } + } } diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index aa8430b8..2079dc14 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -91,16 +91,16 @@ public class LobbyController implements Initializable { ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted()); }); + customizeButton.setOnMouseReleased(event -> { + customizationDialog = createCustomizeDialog(); + Sounds.playButtonClick(); + customizationDialog.show(); + }); + Platform.runLater(() -> { Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId(); playersColor = Colors.getColor(playerId - 1); - customizationDialog = createCustomizeDialog(); - - customizeButton.setOnMouseReleased(event -> { - Sounds.playButtonClick(); - customizationDialog.show(); - }); }); leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound()); diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 1819ff48..76de92d3 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -96,7 +96,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Label timerLabel; @FXML - private StackPane contentAnchorPane; + private StackPane contentStackPane; private GridPane contentGridPane; @FXML @@ -149,8 +149,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Sounds.stopMusic(); Sounds.playRaceMusic(); - finishScreenDialog = createFinishDialog(); - // Load a default important annotation state //importantAnnotations = new ImportantAnnotationsState(); @@ -195,9 +193,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // chatHistory.textProperty().addListener((obs, oldValue, newValue) -> { // chatHistory.setScrollTop(Double.MAX_VALUE); // }); - rvAnchorPane.setOnMouseClicked((event) -> - rvAnchorPane.requestFocus() - ); + + contentStackPane.setOnMouseClicked(event -> { + contentStackPane.requestFocus(); + }); //Makes the chat history non transparent when clicked on chatInput.focusedProperty().addListener(new ChangeListener() { @@ -215,26 +214,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel public void showFinishDialog(ArrayList finishedBoats) { raceState.setRaceStarted(false); - finishDialogController.setFinishedBoats(finishedBoats); - finishScreenDialog.show(); + createFinishDialog(finishedBoats); } - private JFXDialog createFinishDialog() { + /** + * Create finishScreenDialog and set up finishDialogController. + */ + private void createFinishDialog(ArrayList finishedBoats) { FXMLLoader dialog = new FXMLLoader( getClass().getResource("/views/dialogs/RaceFinishDialog.fxml")); - JFXDialog finishScreenDialog = null; - - try { - finishScreenDialog = new JFXDialog(contentAnchorPane, dialog.load(), - JFXDialog.DialogTransition.CENTER); - } catch (IOException e) { - e.printStackTrace(); - } - - finishDialogController = dialog.getController(); - - return finishScreenDialog; + Platform.runLater(() -> { + try { + finishScreenDialog = new JFXDialog(contentStackPane, dialog.load(), + JFXDialog.DialogTransition.CENTER); + finishDialogController = dialog.getController(); + finishDialogController.setFinishedBoats(finishedBoats); + finishScreenDialog.show(); + } catch (IOException e) { + e.printStackTrace(); + } + }); } @@ -264,7 +264,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel gameView = new GameView3D(); // gameView.setFrameRateFXText(fpsDisplay); Platform.runLater(() -> { - contentAnchorPane.getChildren().add(0, gameView.getAssets()); + contentStackPane.getChildren().add(0, gameView.getAssets()); ((SubScene) gameView.getAssets()).widthProperty() .bind(ViewManager.getInstance().getStage().widthProperty()); ((SubScene) gameView.getAssets()).heightProperty() @@ -879,7 +879,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel public String readChatInput() { String chat = chatInput.getText(); chatInput.clear(); - rvAnchorPane.requestFocus(); + contentStackPane.requestFocus(); return chat; } diff --git a/src/main/java/seng302/visualiser/controllers/ServerListController.java b/src/main/java/seng302/visualiser/controllers/ServerListController.java index 3d457bd4..c6dc4325 100644 --- a/src/main/java/seng302/visualiser/controllers/ServerListController.java +++ b/src/main/java/seng302/visualiser/controllers/ServerListController.java @@ -112,16 +112,23 @@ public class ServerListController implements Initializable, ServerListenerDelega serverListVBox.getChildren().add(noServersFound); // Set up dialog for server creation + serverListHostButton.setOnAction(action -> { + showServerCreationDialog(); + }); + } + + /** + * Shows Server Creation Dialog when "Host" button is clicked. + */ + private void showServerCreationDialog() { Platform.runLater(() -> { FXMLLoader dialogContent = new FXMLLoader(getClass().getResource( "/views/dialogs/ServerCreationDialog.fxml")); try { JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(), DialogTransition.CENTER); - serverListHostButton.setOnAction(action -> { - dialog.show(); - Sounds.playButtonClick(); - }); + dialog.show(); + Sounds.playButtonClick(); } catch (IOException e) { logger.warn("Could not create Server Creation Dialog."); } diff --git a/src/main/java/seng302/visualiser/controllers/ViewManager.java b/src/main/java/seng302/visualiser/controllers/ViewManager.java index 8e966813..8f98824c 100644 --- a/src/main/java/seng302/visualiser/controllers/ViewManager.java +++ b/src/main/java/seng302/visualiser/controllers/ViewManager.java @@ -2,6 +2,9 @@ package seng302.visualiser.controllers; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.controls.JFXDialog; +import com.jfoenix.controls.JFXDialog.DialogTransition; +import com.jfoenix.controls.JFXSnackbar; import com.jfoenix.svg.SVGGlyph; import java.io.IOException; import java.util.HashMap; @@ -15,6 +18,7 @@ import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.image.Image; import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.slf4j.Logger; @@ -23,6 +27,7 @@ import seng302.gameServer.ServerAdvertiser; import seng302.utilities.BonjourInstallChecker; import seng302.utilities.Sounds; import seng302.visualiser.GameClient; +import seng302.visualiser.controllers.dialogs.KeyBindingDialogController; public class ViewManager { @@ -32,12 +37,9 @@ public class ViewManager { private HashMap properties; //TODO is this the best way to do this?? private ObservableList playerList; private Logger logger = LoggerFactory.getLogger(ViewManager.class); - - public Stage getStage() { - return stage; - } - private Stage stage; + private JFXSnackbar jfxSnackbar; + private JFXDialog keyBindingDialog; private ViewManager() { properties = new HashMap<>(); @@ -99,6 +101,8 @@ public class ViewManager { gameClient.stopGame(); System.exit(0); }); + + jfxSnackbar = new JFXSnackbar(decorator); } /** @@ -119,12 +123,31 @@ public class ViewManager { //Get the button box HBox btns = (HBox) decorator.getChildren().get(0); + //Create settings button -- [WIP] + JFXButton btnKeyBinding = new JFXButton(); + btnKeyBinding.setText(" Key Bindings"); + btnKeyBinding.setStyle("-fx-text-fill:#fff"); + btnKeyBinding.getStyleClass().add("jfx-decorator-button"); + btnKeyBinding.setCursor(Cursor.HAND); + btnKeyBinding.setFocusTraversable(false); + + btnKeyBinding.setOnMouseClicked(event -> Platform.runLater(() -> { + try { + if (!checkDialogOpened(decorator.getChildren())) { + showKeyBindingDialog(); + } + } catch (IOException e) { + logger.warn("Something went wrong when opening key bind dialog"); + } + })); + //Create new button JFXButton btnMute = new JFXButton(); btnMute.setText(" Toggle Sound"); btnMute.setStyle("-fx-text-fill:#fff"); btnMute.getStyleClass().add("jfx-decorator-button"); btnMute.setCursor(Cursor.HAND); + btnMute.setFocusTraversable(false); //Create Graphics SVGGlyph spacer = new SVGGlyph(0, "SPACER", "", Color.WHITE); @@ -134,9 +157,13 @@ public class ViewManager { SVGGlyph volumeOff = new SVGGlyph(0, "VOLUME_ON", "M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,7.2 L13.5,9.7 L13.5,9 L13.5,9 Z M16,9 C16,9.9 15.8,10.8 15.5,11.6 L17,13.1 C17.7,11.9 18,10.4 18,8.9 C18,4.6 15,1 11,0.1 L11,2.2 C13.9,3.2 16,5.8 16,9 L16,9 Z M1.3,0 L0,1.3 L4.7,6 L0,6 L0,12 L4,12 L9,17 L9,10.3 L13.3,14.6 C12.6,15.1 11.9,15.5 11,15.8 L11,17.9 C12.4,17.6 13.6,17 14.7,16.1 L16.7,18.1 L18,16.8 L9,7.8 L1.3,0 L1.3,0 Z M9,1 L6.9,3.1 L9,5.2 L9,1 L9,1 Z", Color.WHITE); + SVGGlyph keyBindingGlyph = new SVGGlyph(0, "KEY_BINDING", + "M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z", + Color.WHITE); volumeOn.setSize(16, 16); volumeOff.setSize(16, 16); spacer.setSize(40, 16); + keyBindingGlyph.setSize(24, 16); // Determine which graphic should go on the button if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) { @@ -145,9 +172,12 @@ public class ViewManager { btnMute.setGraphic(volumeOn); } + btnKeyBinding.setGraphic(keyBindingGlyph); + // Add Buttons btns.getChildren().add(0, spacer); btns.getChildren().add(0, btnMute); + btns.getChildren().add(0, btnKeyBinding); btnMute.setOnAction((action) -> { Sounds.toggleAllSounds(); if (btnMute.getGraphic().equals(volumeOff)) { @@ -159,6 +189,63 @@ public class ViewManager { } + /** + * Recursively find JFXDialog given a starting node. Will traverse children of StackPane. + * + * @param nodes children nodes to be check. + * @return true if node contains JFXDialog. + */ + private Boolean checkDialogOpened(ObservableList nodes) { + boolean foundJFXDialog = false; + for (Node node : nodes) { + if (node instanceof JFXDialog) { + return true; + } else if (node instanceof StackPane) { + foundJFXDialog = checkDialogOpened(((StackPane) node).getChildren()); + } + } + return foundJFXDialog; + } + + private void showKeyBindingDialog() throws IOException { + FXMLLoader dialogContent = new FXMLLoader(getClass().getResource( + "/views/dialogs/KeyBindingDialog.fxml")); + for (Node node : decorator.getChildren()) { + if (node instanceof StackPane) { + keyBindingDialog = new JFXDialog((StackPane) node, + dialogContent.load(), + DialogTransition.CENTER); + + KeyBindingDialogController keyBindingDialogController = dialogContent + .getController(); + keyBindingDialogController.setGameClient(this.gameClient); + keyBindingDialog.show(); + Sounds.playButtonClick(); + } + } + } + + public void closeKeyBindingDialog() { + keyBindingDialog.close(); + } + + /** + * Show a snackbar at the bottom of the app for 1 second. + * + * @param snackbarText text to be displayed. + */ + public void showSnackbar(String snackbarText, boolean isWarning) { + if (isWarning) { + decorator.getStylesheets() + .add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm()); + } else { + if (decorator.getStylesheets().size() > 1) { + decorator.getStylesheets().remove(1); + } + } + jfxSnackbar.show(snackbarText, 1500); + } + /** * Determines if a PC has compatibility with the bonjour protocol for server detection. */ @@ -221,6 +308,7 @@ public class ViewManager { /** * Change the view to the Lobby Screen + * * @param disableReadyButton Boolean value so that clients can't try start a game. * @return A LobbyController object for the Lobby Screen. */ @@ -243,6 +331,7 @@ public class ViewManager { /** * Sets up the view for the race. Creating a new decorator and destroying the old one. + * * @return A RaceViewController for the race view screen. */ @@ -267,15 +356,6 @@ public class ViewManager { scene.setOnKeyPressed(gameClient::keyPressed); scene.setOnKeyReleased(gameClient::keyReleased); - // uncomment to make it full screen -// Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds(); -// stage.setX(visualBounds.getMinX()); -// stage.setY(visualBounds.getMinY()); -// stage.setWidth(visualBounds.getWidth()); -// stage.setHeight(visualBounds.getHeight()); -// stage.setMaximized(true); -// stage.setFullScreen(true); - stage.setMinHeight(500); stage.setMinWidth(800); stage.setTitle("Party Parrots At Sea"); @@ -288,7 +368,7 @@ public class ViewManager { } }); - while (loader.getController() == null){ + while (loader.getController() == null) { try { Thread.sleep(50); } catch (InterruptedException e) { @@ -298,4 +378,9 @@ public class ViewManager { return loader.getController(); } + + public Stage getStage() { + return stage; + } + } diff --git a/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java new file mode 100644 index 00000000..2a286ec4 --- /dev/null +++ b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java @@ -0,0 +1,185 @@ +package seng302.visualiser.controllers.dialogs; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXToggleButton; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.input.KeyEvent; +import seng302.model.GameKeyBind; +import seng302.model.KeyAction; +import seng302.visualiser.GameClient; +import seng302.visualiser.controllers.ViewManager; + +public class KeyBindingDialogController implements Initializable { + + //--------FXML BEGIN--------// + @FXML + private Label keyBindingDialogHeader; + @FXML + private Label closeLabel; + @FXML + private JFXButton zoomInbtn; + @FXML + private JFXButton zoomOutBtn; + @FXML + private JFXButton vmgBtn; + @FXML + private JFXButton sailInOutBtn; + @FXML + private JFXButton tackGybeBtn; + @FXML + private JFXButton upwindBtn; + @FXML + private JFXButton downwindBtn; + @FXML + private JFXButton resetBtn; + @FXML + private Label upwindLabel; + @FXML + private Label downwindLabel; + @FXML + private JFXToggleButton turningToggle; + //---------FXML END---------// + + private GameKeyBind gameKeyBind; + private List buttons = new ArrayList<>(); + private Map buttonActionMap; + private GameClient gameClient; // to send turning mode packet + + @Override + public void initialize(URL location, ResourceBundle resources) { + gameKeyBind = GameKeyBind.getInstance(); + buttons = new ArrayList<>(); + Collections.addAll(buttons, + zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn); + bindButtonWithAction(); + loadKeyBind(); + + buttons.forEach(button -> { + button.setOnMouseEntered(event -> mouseEnter(button)); + button.setOnMousePressed(event -> buttonPressed(button)); + button.setOnMouseExited(event -> mouseExit(button)); + button.setOnKeyPressed(event -> keyPressed(event, button)); + }); + + turningToggle.setOnMouseClicked(event -> toggleTurningMode()); + + resetBtn.setOnMouseClicked(event -> { + gameKeyBind.setToDefault(); + loadKeyBind(); + }); + + closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog()); + + keyBindingDialogHeader.setFocusTraversable(true); + keyBindingDialogHeader.requestFocus(); + } + + /** + * Set buttons' label according to GameKeyBind settings + */ + private void loadKeyBind() { + buttons.forEach( + button -> button + .setText(gameKeyBind.getKeyCode(buttonActionMap.get(button)).getName())); + turningToggle.setSelected(gameKeyBind.isContinuouslyTurning()); + if (gameKeyBind.isContinuouslyTurning()) { + upwindLabel.setText("ClOCKWISE TURNING"); + downwindLabel.setText("ANTICLOCKWISE TURNING"); + } else { + upwindLabel.setText("UPWIND"); + downwindLabel.setText("DOWNWIND"); + } + } + + /** + * Bind buttons with specific action in a map. + */ + private void bindButtonWithAction() { + buttonActionMap = new HashMap<>(); + for (int i = 0; i < 7; i++) { + buttonActionMap.put(buttons.get(i), KeyAction.getType(i + 1)); + } + } + + /** + * Prompt success / failure message for reassigning key action + */ + private void showSnackBar(String message, Boolean isWarning) { + ViewManager.getInstance().showSnackbar(message, isWarning); + } + + /** + * When a mouse enters the button, the color and font size should change to highlight + * @param button + */ + private void mouseEnter(Button button) { + button.setStyle("" + + "-fx-background-color: -fx-pp-theme-color;" + + "-fx-text-fill: -fx-pp-front-color;" + + "-fx-font-size: 15;"); + } + + /** + * Prompt "press key..." to inform users assign a new key bind by pressing a key + * @param button + */ + private void buttonPressed(Button button) { + button.setText("PRESS KEY..."); + } + + + /** + * When mouse leaves the button, return the button to the normal state in terms of text, + * color and font size + * @param button + */ + private void mouseExit(Button button) { + button.setText(GameKeyBind.getInstance().getKeyCode(buttonActionMap.get(button)).getName()); + button.setStyle("" + + "-fx-background-color: -fx-pp-front-color; " + + "-fx-text-fill: -fx-pp-theme-color; " + + "-fx-font-size: 13;"); + } + + /** + * When a key is pressed, check if the new binding conflicts to any existed settings, if not + * assign the selected action with the new key binding to GameKeyBind. + * @param event + * @param button + */ + private void keyPressed(KeyEvent event, Button button) { + event.consume(); + KeyAction buttonAction = buttonActionMap.get(button); + if (gameKeyBind.bindKeyToAction(event.getCode(), buttonAction)) { + showSnackBar(button.getId() + " is set to " + event.getCode().getName(), false); + button.setText(gameKeyBind.getKeyCode(buttonAction).getName()); + } else { + loadKeyBind(); + showSnackBar(event.getCode().getName() + " is already in use", true); + } + } + + /** + * When the turning mode is toggled, update gameKeyBind and send out packet to notify the server + */ + private void toggleTurningMode() { + gameKeyBind.toggleTurningMode(); + gameClient.sendToggleTurningModePacket(); + loadKeyBind(); + } + + public void setGameClient(GameClient gameClient) { + this.gameClient = gameClient; + } + +} diff --git a/src/main/resources/css/Master.css b/src/main/resources/css/Master.css index 8636a88a..cb50a645 100644 --- a/src/main/resources/css/Master.css +++ b/src/main/resources/css/Master.css @@ -44,6 +44,11 @@ -fx-border-color: -fx-decorator-color; -fx-border-width: 0 4 4 4; } + +.jfx-decorator-button { + -fx-focus-traversable: false; /* so decorator button will not be focused */ +} + /********* customised scroll bar for scroll pane ***********/ /* The main scrollbar **track** CSS class */ @@ -99,4 +104,16 @@ .slider .track { -fx-background-color: -fx-pp-dark-text-color; +} + +.jfx-snackbar-content { + -fx-background-color: -fx-pp-front-color; + -fx-padding: 0 5 0 5; + -fx-spacing: 0 5 0 5; + -fx-font-size: 15; +} + +.jfx-snackbar-toast { + -fx-text-fill: -fx-pp-theme-color; + -fx-font-size: 15; } \ No newline at end of file diff --git a/src/main/resources/css/RaceView.css b/src/main/resources/css/RaceView.css index 11e8f285..f38216c8 100644 --- a/src/main/resources/css/RaceView.css +++ b/src/main/resources/css/RaceView.css @@ -48,6 +48,7 @@ GridPane .timer * { -fx-text-fill: -fx-pp-theme-color; -fx-font-size: 13px; -fx-pref-height: 35px; + -fx-focus-traversable: false; } #chatSend:hover { diff --git a/src/main/resources/css/StartScreenView.css b/src/main/resources/css/StartScreenView.css index 04338028..bffc296f 100644 --- a/src/main/resources/css/StartScreenView.css +++ b/src/main/resources/css/StartScreenView.css @@ -2,6 +2,7 @@ -fx-font-size: 20px; -fx-text-fill: -fx-pp-light-text-color; -fx-background-color: -fx-pp-theme-color; + -fx-focus-traversable: false; } .jfx-rippler { diff --git a/src/main/resources/css/dialogs/KeyBindingDialog.css b/src/main/resources/css/dialogs/KeyBindingDialog.css new file mode 100644 index 00000000..8d09e130 --- /dev/null +++ b/src/main/resources/css/dialogs/KeyBindingDialog.css @@ -0,0 +1,54 @@ +#keyBindingDialogHeader { + -fx-font-size: 27px; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#closeLabel { + -fx-font-size: 30; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#closeLabel:hover { + -fx-text-fill: -fx-pp-theme-color; + -fx-font-size: 33; +} + +JFXButton { + -fx-background-color: -fx-pp-light-text-color; + -fx-text-fill: -fx-pp-theme-color; + -fx-font-size: 13px; +} + +Label { + -fx-font-size: 15px; + -fx-text-fill: -fx-pp-theme-color; + -fx-effect: -fx-pp-dropshadow-light; +} + +JFXToggleButton { + -jfx-toggle-color: -fx-pp-theme-color; + -fx-text-fill: -fx-pp-theme-color; +} + +#resetBtn { + -fx-background-color: -fx-pp-theme-color; + -fx-text-fill: -fx-pp-front-color; + -fx-effect: -fx-pp-dropshadow-light; + -fx-font-size: 18; +} + +#resetBtn:hover { + -fx-font-size: 20; +} + +.jfx-snackbar-content { + -fx-background-color: #323232; +} + +.jfx-snackbar-toast { + -fx-text-fill: WHITE; +} + +.jfx-snackbar-action { + -fx-text-fill: #ff4081; +} \ No newline at end of file diff --git a/src/main/resources/css/dialogs/Snackbar.css b/src/main/resources/css/dialogs/Snackbar.css new file mode 100644 index 00000000..0fbe23b2 --- /dev/null +++ b/src/main/resources/css/dialogs/Snackbar.css @@ -0,0 +1,4 @@ +/* a separate file to dynamically change snackbar's color */ +.jfx-snackbar-toast { + -fx-text-fill: red !important; +} \ No newline at end of file diff --git a/src/main/resources/views/dialogs/KeyBindingDialog.fxml b/src/main/resources/views/dialogs/KeyBindingDialog.fxml new file mode 100644 index 00000000..4a677797 --- /dev/null +++ b/src/main/resources/views/dialogs/KeyBindingDialog.fxml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +