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.model.generators.ChaosGameDescriptionFactory; import edu.ntnu.stud.chaosgame.model.transformations.AffineTransform2D; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Duration; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; public class ChaosGameGuiView implements ChaosGameObserver { private int currentLine = 0; /** * The ChaosCanvas for this GUI. */ private ChaosCanvas canvas; /** * The ChaosGameDescription. */ private ChaosGameDescription description; /** * The ChaosGameDescriptionFactory. */ private ChaosGameDescriptionFactory factory; /** * The AtomicReference for the ChaosGameDescription. */ private AtomicReference<ChaosGameDescription> descriptionRef; /** * The PixelWriter for the GUI. */ private PixelWriter pixelWriter; /** * 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, and quit buttons for the GUI. */ private Button startButton; private Button stopButton; private Button newButton; private Button clearButton; private Button quitButton; /** * 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 final int resolutionHeight = 1000; private final int resolutionWidth = 1000; public ChaosGameGuiView(Stage primaryStage) throws IOException { this.initializeComponents(); 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 -> this.drawChaosGame())); this.initializeImageView(); this.timeline.setCycleCount(Timeline.INDEFINITE); // Side menu //TEMPORARY CODE to test Chaos Games in GUI this.initializeGameComponents(); 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 -> timeline.play()); this.stopButton = new Button("Stop"); stopButton.setOnAction(event -> timeline.stop()); this.newButton = new Button("New"); newButton.setOnAction(event ->{ WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); canvas.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.descriptionRef = new AtomicReference<>(factory.getDescriptions().get(0)); this.description = descriptionRef.get(); this.canvas = new ChaosCanvas(resolutionWidth, resolutionHeight, descriptionRef.get().getMinCoords(), descriptionRef.get().getMaxCoords()); game = new ChaosGame(this.description, canvas); } /** * Initialize components related to the image view and zoom function. */ private void initializeImageView() { // Image view this.imageView = new ChaosGameImageView(this); width = resolutionWidth; height = resolutionHeight; WritableImage writableImage = new WritableImage(width, height); pixelWriter = writableImage.getPixelWriter(); this.imageView.setImage(writableImage); this.imageView.setFitHeight(1000); this.imageView.setFitWidth(1000); this.fillImageView(); /*imageView.setOnScroll(event -> { double zoomFactor = 1.05; // This is the zoom factor per scroll double movement = event.getDeltaY(); imageView.setTranslateX(imageView.getTranslateX() - event.getDeltaX()); imageView.setTranslateY(imageView.getTranslateY() - event.getDeltaY()); if (movement > 0) { imageView.setScaleX(imageView.getScaleX() * zoomFactor); imageView.setScaleY(imageView.getScaleY() * zoomFactor); } else { imageView.setScaleX(imageView.getScaleX() / zoomFactor); imageView.setScaleY(imageView.getScaleY() / zoomFactor); } // Limit the zoom so the scale doesn't become too big or too small imageView.setScaleX(Math.max(imageView.getScaleX(), 1.0)); imageView.setScaleY(Math.max(imageView.getScaleY(), 1.0)); imageView.setScaleX(Math.min(imageView.getScaleX(), 10.0)); // max scale imageView.setScaleY(Math.min(imageView.getScaleY(), 10.0)); // max scale event.consume(); // consume the event so it doesn't propagate further });*/ } /** * Color the entire image view white to facilitate scrolling. */ private void fillImageView() { // Color the image white for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { pixelWriter.setColor(x, y, Color.WHITE); } } } /** * 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); AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(canvas); // Set action for Sierpinski radio button. sierpinskiRadioButton.setOnAction(event -> { timeline.stop(); //canvasRef.get().clearCanvas(); WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); this.fillImageView(); //canvas.clearCanvas(); this.descriptionRef.set(factory.getDescriptions().get(0)); // Assuming the Sierpinski description is at index 0 canvasRef.set(new ChaosCanvas(resolutionWidth, resolutionHeight, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); game = new ChaosGame(this.descriptionRef.get(), canvasRef.get()); }); // Set action for Barnsley radio button. barnsleyRadioButton.setOnAction(event -> { timeline.stop(); //canvasRef.get().clearCanvas(); WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); this.fillImageView(); //canvas.clearCanvas(); this.descriptionRef.set(factory.getDescriptions().get(1)); // Assuming the Sierpinski description is at index 0 canvasRef.set(new ChaosCanvas(resolutionWidth, resolutionHeight, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); game = new ChaosGame(this.descriptionRef.get(), canvasRef.get()); }); // Set action for Julia radio button. juliaRadioButton.setOnAction(event -> { timeline.stop(); //canvasRef.get().clearCanvas(); WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); this.fillImageView(); //canvas.clearCanvas(); this.descriptionRef.set(factory.getDescriptions().get(2)); // Assuming the Sierpinski description is at index 0 canvasRef.set(new ChaosCanvas(resolutionWidth, resolutionHeight, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); game = new ChaosGame(this.descriptionRef.get(), canvasRef.get()); }); improvedBarnsleyButton.setOnAction(event -> { timeline.stop(); //canvasRef.get().clearCanvas(); WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); this.fillImageView(); //canvas.clearCanvas(); // Test this.descriptionRef.set(factory.getDescriptions().get(3)); // Assuming the Sierpinski description is at index 0 this.description = factory.getDescriptions().get(3); for (int i = 0; i < this.description.getTransforms().size(); i++) { System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getMatrix().getA00()); System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getMatrix().getA01()); System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getMatrix().getA10()); System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getMatrix().getA11()); System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getVector().getX0()); System.out.println(((AffineTransform2D) this.description.getTransforms().get(i)).getVector().getX1()); } canvasRef.set(new ChaosCanvas(resolutionWidth, resolutionHeight, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); game = new ChaosGame(this.descriptionRef.get(), canvasRef.get()); }); // 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.sideMenu = new VBox(); // Parameters VBox parameterBox = new VBox(); // Step Count GUI VBox stepCountBox = new VBox(); Label stepCountLabel = new Label("Step Count"); TextArea stepCountTextArea = new TextArea(); stepCountTextArea.setPrefHeight(5); stepCountTextArea.setPrefWidth(50); Button changeStepCountButton = new Button("Change Step Count"); stepCountBox.getChildren().addAll(stepCountLabel,stepCountTextArea, changeStepCountButton); // Minimum Coordinates GUI VBox minCoordinatesBox = new VBox(); Label minCoordinatesLabel = new Label("Min. Coordinates"); TextArea minimumCoordinatesTextArea = new TextArea(); minimumCoordinatesTextArea.setPrefHeight(5); minimumCoordinatesTextArea.setPrefWidth(50); Button changeMinimumCoordinatesButton = new Button("Change Min. Coordinates"); minCoordinatesBox.getChildren().addAll(minCoordinatesLabel,minimumCoordinatesTextArea,changeMinimumCoordinatesButton); // Maximum Coordinates GUI VBox maxCoordinatesBox = new VBox(); Label maxCoordinatesLabel = new Label("Max Coordinates"); TextArea maximumCoordinatesTextArea = new TextArea(); maximumCoordinatesTextArea.setPrefHeight(5); maximumCoordinatesTextArea.setPrefWidth(50); Button changeMaximumCoordinatesButton = new Button("Change Max Coordinates"); maxCoordinatesBox.getChildren().addAll(maxCoordinatesLabel,maximumCoordinatesTextArea,changeMaximumCoordinatesButton); // 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); // 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)); this.borderPane = new BorderPane(); this.borderPane.setCenter(imageView); this.borderPane.setRight(sideMenu); imageView.setFocusTraversable(true); sideMenu.setFocusTraversable(false); borderPane.setFocusTraversable(false); } public void drawChaosGame(){ ChaosCanvas canvas = game.getCanvas(); game.runSteps(100000); // Test implementation for drawing fractals int[][] betaArray = canvas.getCanvasArray(); for (int i = 0; i < canvas.getWidth(); i++) { for (int j = 0; j < canvas.getHeight(); j++) { if (betaArray[i][j] == 1) { pixelWriter.setColor(j,i,Color.BLACK); } } } } 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 setPixelWriter(PixelWriter pixelWriter) { this.pixelWriter = pixelWriter; } public void setImageViewFromImage(Image inputView) { this.imageView.setImage(inputView); } /** * Update the description of the chaos game. * TODO: this method may need to be changed depending on how we implement the UI. * * @param description the description. */ @Override public void updateDescription(ChaosGameDescription description) { this.game.setDescription(description); } /** * 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) { //drawChaosGame(); } }