Skip to content
Snippets Groups Projects
ChaosGameGui.java 14 KiB
Newer Older
Magnus Eik's avatar
Magnus Eik committed
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.data.Vector2D;
Magnus Eik's avatar
Magnus Eik committed
import edu.ntnu.stud.chaosgame.model.generators.ChaosGameDescriptionFactory;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.geometry.Insets;
Magnus Eik's avatar
Magnus Eik committed
import javafx.scene.Scene;
import javafx.scene.control.*;
Magnus Eik's avatar
Magnus Eik committed
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;
Magnus Eik's avatar
Magnus Eik committed
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
Magnus Eik's avatar
Magnus Eik committed

import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
public class ChaosGameGui implements ChaosGameObserver {
Håvard Daleng's avatar
Håvard Daleng committed
  private int currentLine = 0;

Håvard Daleng's avatar
Håvard Daleng committed
  /**
   * The ChaosCanvas for this GUI.
   */
Håvard Daleng's avatar
Håvard Daleng committed

  /**
   * The ChaosGameDescription.
   */
  private ChaosGameDescription description;

  /**
   * The ChaosGameDescriptionFactory.
   */
  private ChaosGameDescriptionFactory factory;

  /**
   * The AtomicReference for the ChaosGameDescription.
   */
  private AtomicReference<ChaosGameDescription> descriptionRef;

  /**
   * The ImageView for the GUI..
   */
  private ChaosGameImageView imageView;
Håvard Daleng's avatar
Håvard Daleng committed

  /**
   * 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.
Håvard Daleng's avatar
Håvard Daleng committed
   */
  private Button startButton;
  private Button stopButton;
  private Button newButton;
  private Button clearButton;
  private Button quitButton;
Håvard Daleng's avatar
Håvard Daleng committed

  /**
   * 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;


  public ChaosGameGui(Stage primaryStage) throws IOException {
Håvard Daleng's avatar
Håvard Daleng committed

    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();

    // 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 ->{
      this.canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
      chaosCanvas.clearCanvas();
Håvard Daleng's avatar
Håvard Daleng committed
    });

    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.chaosCanvas = new ChaosCanvas(100, 100, descriptionRef.get().getMinCoords(), descriptionRef.get().getMaxCoords());
    game = new ChaosGame(this.description, chaosCanvas);
Håvard Daleng's avatar
Håvard Daleng committed
  }

  /**
   * Initialize components related to the image view and zoom function.
   */
  private void initializeImageView() {
    // Image view
    this.imageView = new ChaosGameImageView(this);
Håvard Daleng's avatar
Håvard Daleng committed
    width = 1000;
    height = 1000;
    this.canvas = new Canvas(width, height);
    this.imageView.setImage(canvas.snapshot(null, null));
   * Color the entire image view white.
    // Color the image white
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        this.canvas.getGraphicsContext2D().setFill(Color.WHITE);
        this.canvas.getGraphicsContext2D().fillRect(i, j, 1, 1);
Håvard Daleng's avatar
Håvard Daleng committed
  /**
   * 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<>(chaosCanvas);
Håvard Daleng's avatar
Håvard Daleng committed

    // Set action for Sierpinski radio button.
    sierpinskiRadioButton.setOnAction(event -> {
Håvard Daleng's avatar
Håvard Daleng committed
    });

    // Set action for Barnsley radio button.
    barnsleyRadioButton.setOnAction(event -> {
Håvard Daleng's avatar
Håvard Daleng committed
    });

    // Set action for Julia radio button.
    juliaRadioButton.setOnAction(event -> {
Håvard Daleng's avatar
Håvard Daleng committed
    });

    improvedBarnsleyButton.setOnAction(event -> {
      /*for (int i = 0; i < this.description.getTransforms().size(); i++) {
Håvard Daleng's avatar
Håvard Daleng committed
        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());
Håvard Daleng's avatar
Håvard Daleng committed

    });

    // 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));

    // 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);

Håvard Daleng's avatar
Håvard Daleng committed
    this.borderPane = new BorderPane();
    this.borderPane.setCenter(imageView);
    this.borderPane.setRight(rightVBox);
Håvard Daleng's avatar
Håvard Daleng committed
    imageView.setFocusTraversable(true);
    rightVBox.setFocusTraversable(false);
Håvard Daleng's avatar
Håvard Daleng committed
    borderPane.setFocusTraversable(false);

  }

  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("<<");
      }
    });
  }

  /**
   * Get the chaos canvas of this GUI view.
   *
   * @return the canvas.
   */
  public ChaosCanvas getChaosCanvas() {
    return this.chaosCanvas;
Håvard Daleng's avatar
Håvard Daleng committed
  public void drawChaosGame(){
    ChaosCanvasToImageConverter converter = new ChaosCanvasToImageConverter(this.chaosCanvas);
    WritableImage image = converter.getImage();
    this.canvas.getGraphicsContext2D().drawImage(image, 0, 0);
    this.imageView.setImage(image);
Håvard Daleng's avatar
Håvard Daleng committed
  }

  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 description of the chaos game.
   * TODO: this method may need to be changed depending on how we implement the UI. Rename to update?
   *
   * @param index the index of the new description in the list of factory descriptions.
Håvard Daleng's avatar
Håvard Daleng committed
   *
   */
  @Override
  public void updateDescription(int index) {
    AtomicReference<ChaosCanvas> canvasRef = new AtomicReference<>(chaosCanvas);
    timeline.stop();
    canvasRef.get().clearCanvas();
    this.canvas.getGraphicsContext2D().clearRect(0, 0, this.canvas.getGraphicsContext2D().
        getCanvas().getWidth(), this.canvas.getGraphicsContext2D().getCanvas().getHeight());

    this.descriptionRef.set(factory.getDescriptions().get(index)); // Assuming the Sierpinski description is at index 0
    canvasRef.set(new ChaosCanvas(1000, 1000, this.descriptionRef.get().getMinCoords(),
        this.descriptionRef.get().getMaxCoords()));
    this.chaosCanvas = canvasRef.get();
    game = new ChaosGame(this.descriptionRef.get(), canvasRef.get());
    //this.game.setDescription(description);
  }

  /**
   * 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);
    this.game.setCurrentPoint(new Vector2D(centreX, centreY));
Håvard Daleng's avatar
Håvard Daleng committed
  }

  /**
   * 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();
  }