package edu.ntnu.stud.chaosgame.view; import edu.ntnu.stud.chaosgame.controller.game.ChaosCanvas; import edu.ntnu.stud.chaosgame.controller.game.ChaosGame; import edu.ntnu.stud.chaosgame.controller.game.ChaosGameDescription; import edu.ntnu.stud.chaosgame.controller.game.GuiButtonController; import edu.ntnu.stud.chaosgame.controller.utility.Formatter; import edu.ntnu.stud.chaosgame.model.data.Vector2D; import edu.ntnu.stud.chaosgame.model.generators.ChaosGameDescriptionFactory; import java.util.Objects; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.image.WritableImage; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Duration; import java.io.IOException; public class ChaosGameGui implements ChaosGameObserver { private int currentLine = 0; private Canvas canvas; /** * The ChaosCanvas for this GUI. */ private ChaosCanvas chaosCanvas; /** * The ChaosGameDescription. */ private ChaosGameDescription description; /** * The ChaosGameDescriptionFactory. */ private ChaosGameDescriptionFactory factory; /** * The ImageView for the GUI.. */ private ChaosGameImageView imageView; /** * The Scene for the GUI.. */ private Scene scene; /** * The width and height of the GUI. */ private int width; private int height; /** * The ChaosGame for this GUI. */ private ChaosGame game; /** * The Timeline for the GUI. */ private Timeline timeline; /** * The BorderPane for the GUI. */ private BorderPane borderPane; /** * The side menu for the GUI. */ private VBox sideMenu; /** * The start, stop, new, clear, quit and show sidebar buttons for the GUI. */ private Button startButton; private Button stopButton; private Button newButton; private Button clearButton; private Button quitButton; private Button sideMenuButton; /** * The load fractal from file and write fractal to file buttons for the GUI. */ private Button loadFractalFromFileButton; private Button writeFractalToFileButton; /** * The radio buttons for the fractal type for the GUI. */ private RadioButton sierpinskiRadioButton; private RadioButton barnsleyRadioButton; private RadioButton juliaRadioButton; private RadioButton improvedBarnsleyButton; private TextField stepCountTextField; private CheckBox colorCheckBox; private GuiButtonController controller; public ChaosGameGui(Stage primaryStage) throws IOException { this.initializeComponents(); this.initializeGameComponents(); this.controller = new GuiButtonController(game, this); // Initialize controller here primaryStage.setTitle("Fractal Chaos Game"); primaryStage.setScene(scene); primaryStage.setOnShown(event -> this.imageView.requestFocus()); primaryStage.show(); } /** * Initialize the components of the GUI. */ private void initializeComponents() { // Timeline //this.timeline = new Timeline(new KeyFrame(Duration.seconds(0.05), event -> controller.drawChaosGame())); this.initializeImageView(); // Side menu //this.initializeMainButtons(); this.initializeFractalButtons(); this.initializeSideMenu(); this.scene = new Scene(this.borderPane,1700,1000); } /** * Initialize the main buttons for the GUI. */ // private void initializeMainButtons() { // this.startButton = new Button("Start"); // startButton.setOnAction(event -> controller.startGame()); // this.stopButton = new Button("Stop"); // stopButton.setOnAction(event -> controller.stopGame()); // // this.newButton = new Button("New"); // // newButton.setOnAction(event ->{ // this.canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); // chaosCanvas.clearCanvas(); // }); // // this.clearButton = new Button("Clear"); // // clearButton.setOnAction(event -> { // getImageView().setImage(null); // setCurrentLine(0); // }); // // // Quit button // this.quitButton = new Button("Quit"); // quitButton.setOnAction(event -> Platform.exit()); // // } /** * Initialize the components related to the chaos game itself. */ private void initializeGameComponents() { // Description this.factory = new ChaosGameDescriptionFactory(); this.description = factory.getDescriptions().get(0); this.chaosCanvas = new ChaosCanvas(1000, 1000, this.description.getMinCoords(), this.description.getMaxCoords()); game = new ChaosGame(this.description, chaosCanvas); //controller.startGame(); // Start the game after it's created } /** * Initialize components related to the image view and zoom function. */ private void initializeImageView() { // Image view this.imageView = new ChaosGameImageView(this); width = 1000; height = 1000; this.canvas = new Canvas(width, height); //this.imageView.setImage(canvas.snapshot(null, null)); this.clearImageView(); } /** * Color the entire image view white. */ public void clearImageView() { GraphicsContext gc = canvas.getGraphicsContext2D(); gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); imageView.setImage(null); } /** * Initialize the buttons related to managing the fractals. */ private void initializeFractalButtons() { // Radio buttons for choosing fractal type ToggleGroup group = new ToggleGroup(); this.sierpinskiRadioButton = new RadioButton("Sierpinski"); sierpinskiRadioButton.setToggleGroup(group); //sierpinskiRadioButton.setSelected(true); this.barnsleyRadioButton = new RadioButton("Barnsley"); barnsleyRadioButton.setToggleGroup(group); this.juliaRadioButton = new RadioButton("Julia"); juliaRadioButton.setToggleGroup(group); this.improvedBarnsleyButton = new RadioButton("Improved Barnsley"); improvedBarnsleyButton.setToggleGroup(group); // Set action for Sierpinski radio button. sierpinskiRadioButton.setOnAction(event -> { controller.updateDescription(0); }); // Set action for Barnsley radio button. barnsleyRadioButton.setOnAction(event -> { controller.updateDescription(1); }); // Set action for Julia radio button. juliaRadioButton.setOnAction(event -> { controller.updateDescription(2); }); improvedBarnsleyButton.setOnAction(event -> { controller.updateDescription(3); }); // Load fractal file button this.loadFractalFromFileButton = new Button("Load Fractal"); // Write fractal to file button this.writeFractalToFileButton = new Button("Write to File"); } /** * Initialize the side menu. */ private void initializeSideMenu() { this.startButton = new Button("Start"); this.stopButton = new Button("Stop"); this.newButton = new Button("New"); this.clearButton = new Button("Clear"); this.quitButton = new Button("Quit"); this.sideMenuButton = new Button("Side Menu"); this.sideMenu = new VBox(); // Parameters VBox parameterBox = new VBox(); // Step Count GUI VBox stepCountBox = new VBox(); Label stepCountLabel = new Label("Step Count"); this.stepCountTextField = new TextField(); this.stepCountTextField.setTextFormatter(Formatter.getIntFormatter()); // Set formatter stepCountTextField.setPrefHeight(5); stepCountTextField.setPrefWidth(50); stepCountBox.getChildren().addAll(stepCountLabel,stepCountTextField); // Minimum Coordinates GUI VBox minCoordinatesBox = new VBox(); Label minCoordinatesLabel = new Label("Min. Coordinates"); TextField minimumCoordinatesTextField = new TextField(); minimumCoordinatesTextField.setPrefHeight(5); minimumCoordinatesTextField.setPrefWidth(50); Button changeMinimumCoordinatesButton = new Button("Change Min. Coordinates"); minCoordinatesBox.getChildren().addAll(minCoordinatesLabel, minimumCoordinatesTextField,changeMinimumCoordinatesButton); // Maximum Coordinates GUI VBox maxCoordinatesBox = new VBox(); Label maxCoordinatesLabel = new Label("Max Coordinates"); TextField maximumCoordinatesTextField = new TextField(); maximumCoordinatesTextField.setPrefHeight(5); maximumCoordinatesTextField.setPrefWidth(50); Button changeMaximumCoordinatesButton = new Button("Change Max Coordinates"); maxCoordinatesBox.getChildren().addAll(maxCoordinatesLabel, maximumCoordinatesTextField,changeMaximumCoordinatesButton); HBox colorBox = new HBox(); Label colorLabel = new Label("Use color"); this.colorCheckBox = new CheckBox(); Region colorRegion = new Region(); colorRegion.setMinWidth(30); colorBox.getChildren().addAll(colorCheckBox, colorRegion, colorLabel); Region separator1 = new Region(); separator1.setMinHeight(10); Region separator2 = new Region(); separator2.setMinHeight(10); // Fill parameter box parameterBox.getChildren().addAll(stepCountBox, minCoordinatesBox, maxCoordinatesBox); parameterBox.setPadding(new Insets(10)); // Add basic control buttons sideMenu.getChildren().addAll(startButton,stopButton,newButton,clearButton); // Add fractal radio buttons sideMenu.getChildren().addAll(sierpinskiRadioButton, barnsleyRadioButton, juliaRadioButton, improvedBarnsleyButton); sideMenu.getChildren().addAll(separator1, colorBox, separator2); this.initializeColorButtonHandler(); // Add parameter VBox sideMenu.getChildren().add(parameterBox); // Add file buttons sideMenu.getChildren().addAll(loadFractalFromFileButton,writeFractalToFileButton); // Add quit button sideMenu.getChildren().add(quitButton); // Add padding sideMenu.setPadding(new Insets(10)); // Create split pane and button to toggle sidebar this.sideMenuButton = new Button(">>"); this.initializeSideButtonHandler(); Region sideMenuButtonRegion = new Region(); sideMenuButtonRegion.setMinWidth(200); HBox sideMenuButtonBox = new HBox(); sideMenuButtonBox.getChildren().addAll(sideMenuButtonRegion, sideMenuButton); // The right VBox containing both the sidebar and the sidebar toggle button. VBox rightVBox = new VBox(); rightVBox.getChildren().addAll(sideMenuButtonBox, sideMenu); this.sideMenu.setStyle("-fx-background-color: lightgrey; -fx-background-radius: 3;"); this.borderPane = new BorderPane(); this.borderPane.setCenter(imageView); this.borderPane.setRight(rightVBox); imageView.setFocusTraversable(true); rightVBox.setFocusTraversable(false); borderPane.setFocusTraversable(false); } /** * Initialise the side bar button handler, allowing the user * to show or hide the right sidebar. */ private void initializeSideButtonHandler() { TranslateTransition openNav = new TranslateTransition(new Duration(350), sideMenu); openNav.setToX(0); TranslateTransition closeNav = new TranslateTransition(new Duration(350), sideMenu); this.sideMenuButton.setOnAction(e -> { if(sideMenu.getTranslateX() != 0){ this.sideMenuButton.setText(">>"); openNav.play(); } else { closeNav.setToX(sideMenu.getWidth()); closeNav.play(); this.sideMenuButton.setText("<<"); } }); } /** * Initialize the color button handler. */ private void initializeColorButtonHandler() { this.colorCheckBox.setOnAction(event -> { controller.game.setUseColor(colorCheckBox.isSelected()); this.clearImageView(); this.chaosCanvas.clearCanvas(); }); } /** * Get the chaos canvas of this GUI view. * * @return the canvas. */ public ChaosCanvas getChaosCanvas() { return this.chaosCanvas; } public int getWidth(){ return this.width; } public int getHeight(){ return this.height; } public ImageView getImageView(){ return this.imageView; } public void setCurrentLine(int currentLine) { this.currentLine = currentLine; } public void setImageViewFromImage(Image inputView) { this.imageView.setImage(inputView); } /** * Update the canvas and set a new zoom factor for the image view based on the ratio * between the old and new canvas heights. * * @param canvas the canvas to update with. */ @Override public void updateCanvas(ChaosCanvas canvas) { float zoomRatio = (float) this.chaosCanvas.getHeight() / canvas.getHeight(); //this.imageView.fixedZoom(zoomRatio); // Set new zoom factor. this.chaosCanvas = canvas; } /** * Update which parts of the fractal are rendered and at what level of detail. * * @param zoomLevel the number of recursive zoom levels. * @param centreX the x-coordinate of the centre of the image view. * @param centreY the y-coordinate of the centre of the image view. */ public void updateDetail(int zoomLevel, double centreX, double centreY) { this.clearImageView(); this.chaosCanvas.clearCanvas(); this.chaosCanvas.updateCoords(centreX, centreY, zoomLevel); controller.game.setCurrentPoint(new Vector2D(centreX, centreY)); } /** * Update the observer based on changes to the chaos game. * TODO: this method may need to be changed depending on how we implement the UI. The update method may need to be split. * * @param game the game this observer is monitoring. */ @Override public void update(ChaosGame game) { controller.drawChaosGame(); } public TextField getStepCountTextField() { return this.stepCountTextField; } public CheckBox getColorCheckBox() { return this.colorCheckBox; } public Canvas getCanvas() { return this.canvas; } public Button getStartButton() { return this.startButton; } public Button getStopButton() { return this.stopButton; } public Button getNewButton() { return this.newButton; } public Button getClearButton() { return this.clearButton; } public Button getQuitButton() { return this.quitButton; } }