Newer
Older
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;
Håvard Daleng
committed
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 javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.geometry.Insets;
import javafx.scene.canvas.Canvas;
Håvard Daleng
committed
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
// todo: look through GUI and get rid of redundancies, clean up code
public class ChaosGameGui implements ChaosGameObserver {
private Canvas canvas;
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;
/**
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 sideMenuButton;
/**
* The load fractal from file and write fractal to file buttons for the GUI.
*/
private Button loadFractalFromFileButton;
/**
* The write fractal to file button for the GUI.
*/
private Button writeFractalToFileButton;
/**
* The radio buttons for the fractal type for the GUI.
*/
private RadioButton sierpinskiRadioButton;
private RadioButton improvedBarnsleyButton;
Håvard Daleng
committed
private TextField stepCountTextField;
Håvard Daleng
committed
private CheckBox colorCheckBox;
private GuiButtonController controller;
/**
* Constructor for the ChaosGameGui.
*
* @param primaryStage the primary stage for the GUI.
* @throws IOException if the GUI fails to initialize.
*/
public ChaosGameGui(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
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 aspect ratio based on initial dimensions
this.aspectRatio = (double) width / height;
// Add listeners to handle window size changes
scene.widthProperty().addListener((observable, oldValue, newValue) -> {
resizeCanvas();
});
scene.heightProperty().addListener((observable, oldValue, newValue) -> {
resizeCanvas();
});
// Bind the width of the sideMenu to the width of the scene
sideMenu.prefWidthProperty().bind(scene.widthProperty().multiply(0.2)); // 20% of the scene width
// Bind the height of the sideMenu to the height of the scene
sideMenu.prefHeightProperty().bind(scene.heightProperty());
}
/**
* Initialize the components of the GUI.
*/
private void initializeComponents() {
// Timeline
//this.timeline = new Timeline(new KeyFrame(Duration.seconds(0.05), event -> controller.drawChaosGame()));
//this.initializeMainButtons();
this.initializeFractalButtons();
this.initializeSideMenu();
this.scene = new Scene(this.borderPane,1700,1000);
}
/**
* Creates a TextField of specific size.
*/
private TextField createCoordinateTextField(String promptText) {
TextField textField = new TextField();
textField.setPrefHeight(5);
textField.setPrefWidth(90);
textField.setPromptText(promptText);
return textField;
}
/**
* Initialize the components related to the chaos game itself.
*/
private void initializeGameComponents() {
// Description
this.factory = new ChaosGameDescriptionFactory();
Håvard Daleng
committed
this.description = factory.getDescriptions().get(0);
this.chaosCanvas = new ChaosCanvas(1000, 1000, this.description.getMinCoords(),
Håvard Daleng
committed
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);
this.canvas = new Canvas(width, height);
Håvard Daleng
committed
//this.imageView.setImage(canvas.snapshot(null, null));
canvas.widthProperty().bind(imageView.fitWidthProperty());
canvas.heightProperty().bind(imageView.fitHeightProperty());
Håvard Daleng
committed
this.clearImageView();
* Color the entire image view white.
Håvard Daleng
committed
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);
this.loadFractalFromFileButton = new Button("Load Fractal");
Tooltip loadFractalFromFileButtonTooltip = new Tooltip("Load a text file describing a new fractal chaos game");
Tooltip.install(loadFractalFromFileButton,loadFractalFromFileButtonTooltip);
// Write fractal to file button and tooltip
this.writeFractalToFileButton = new Button("Write to File");
Tooltip writeFractalToFileButtonTooltip = new Tooltip("Write a text file defining the current fractal chaos game to chosen location");
Tooltip.install(writeFractalToFileButton, writeFractalToFileButtonTooltip);
}
/**
* Initialize the side menu.
*/
private void initializeSideMenu() {
// Create a Border style
Border blackBorder = new Border(new BorderStroke(Color.BLACK,
BorderStrokeStyle.SOLID,
new CornerRadii(10),
new BorderWidths(5)));
// Create Canvas Header
Label canvasLabel = new Label("Play Controls");
canvasLabel.setAlignment(Pos.CENTER);
canvasLabel.setFont(new Font("Arial",20));
// Create Canvas Buttons
this.startButton = new Button("Start");
this.stopButton = new Button("Stop");
this.quitButton = new Button("Quit");
//this.sideMenuButton = new Button("Side Menu");
// Create Tooltips
Tooltip startButtonTooltip = new Tooltip("Starts drawing the current fractal from the selected chaos game");
Tooltip stopButtonTooltip = new Tooltip("Pause drawing current fractal");
Tooltip clearButtonTooltip = new Tooltip("Clear the current fracal");
Tooltip quitButtonTooltip = new Tooltip("Quit the application");
// Attach Tooltips to Buttons
Tooltip.install(startButton,startButtonTooltip);
Tooltip.install(stopButton,stopButtonTooltip);
Tooltip.install(clearButton,clearButtonTooltip);
Tooltip.install(quitButton, quitButtonTooltip);
// Parameters
VBox parameterBox = new VBox();
VBox controlButtonBox = new VBox();
controlButtonBox.setBorder(blackBorder);
controlButtonBox.setPadding(new Insets(5,5,5,5));
VBox radioButtonBox = new VBox();
radioButtonBox.setBorder(blackBorder);
radioButtonBox.setPadding(new Insets(5,5,5,5));
VBox bottomButtonBox = new VBox();
// Step Count GUI
VBox stepCountBox = new VBox();
Label stepCountLabel = new Label("Step Count");
stepCountLabel.setFont(new Font("Arial",20));
stepCountLabel.setAlignment(Pos.CENTER);
Håvard Daleng
committed
this.stepCountTextField = new TextField();
this.stepCountTextField.setTextFormatter(Formatter.getIntFormatter()); // Set formatter
Formatter.limitTextFieldSize(stepCountTextField, 6);
stepCountTextField.setPrefHeight(5);
stepCountTextField.setPrefWidth(50);
Håvard Daleng
committed
stepCountBox.getChildren().addAll(stepCountLabel,stepCountTextField);
stepCountBox.setAlignment(Pos.CENTER);
stepCountBox.setPadding(new Insets(5,5,5,5));
stepCountBox.setBorder(blackBorder);
// Create a Box for Coordinate Controls
VBox coordinateControlBox = new VBox();
coordinateControlBox.setPadding(new Insets(5, 5, 5, 5));
// Coordinate Control GUI
Label coordinateHeader = new Label("Coordinate Control");
coordinateHeader.setFont(new Font("Arial", 20));
coordinateHeader.setAlignment(Pos.CENTER);
coordinateControlBox.getChildren().add(coordinateHeader);
Label minCoordinatesLabel = new Label("Min. Coordinates");
VBox minCoordinatesBox = new VBox();
TextField minimumCoordinatesTextFieldX = createCoordinateTextField("x");
TextField minimumCoordinatesTextFieldY = createCoordinateTextField("y");
HBox minCoordsHBox = new HBox();
minCoordsHBox.getChildren().addAll(minimumCoordinatesTextFieldX,minimumCoordinatesTextFieldY);
Button changeMinimumCoordinatesButton = new Button("Change Min. Coordinates");
Tooltip changeMinimumCoordinatesButtonTooltip = new Tooltip("Change the minimum x and y coordinates for the fractal");
Tooltip.install(changeMinimumCoordinatesButton,changeMinimumCoordinatesButtonTooltip);
minCoordinatesBox.getChildren().addAll(minCoordinatesLabel, minCoordsHBox,
changeMinimumCoordinatesButton);
// Maximum Coordinates GUI
VBox maxCoordinatesBox = new VBox();
Label maxCoordinatesLabel = new Label("Max Coordinates");
TextField maximumCoordinatesTextFieldX = createCoordinateTextField("x");
TextField maximumCoordinatesTextFieldY = createCoordinateTextField("y");
HBox maxCoordsHBox = new HBox();
maxCoordsHBox.getChildren().addAll(maximumCoordinatesTextFieldX,maximumCoordinatesTextFieldY);
Button changeMaximumCoordinatesButton = new Button("Change Max Coordinates");
Tooltip changeMaximumCoordinatesButtonTooltip = new Tooltip("Change the maximum x and y coordinates of the fractal");
Tooltip.install(changeMaximumCoordinatesButton,changeMaximumCoordinatesButtonTooltip);
maxCoordinatesBox.getChildren().addAll(maxCoordinatesLabel,
maxCoordsHBox,changeMaximumCoordinatesButton);
coordinateControlBox.getChildren().addAll(minCoordinatesBox,maxCoordinatesBox);
coordinateControlBox.setAlignment(Pos.CENTER);
coordinateControlBox.setBorder(blackBorder);
Label colorHeaderLabel = new Label("Color Control");
colorHeaderLabel.setFont(new Font("Arial",20));
colorHeaderLabel.setAlignment(Pos.CENTER);
VBox colorVBox = new VBox();
Håvard Daleng
committed
HBox colorBox = new HBox();
Håvard Daleng
committed
this.colorCheckBox = new CheckBox();
Tooltip colorCheckBoxTooltip = new Tooltip("Change pixel color for pixels drawn multiple times");
Tooltip.install(colorCheckBox,colorCheckBoxTooltip);
Håvard Daleng
committed
Region colorRegion = new Region();
colorRegion.setMinWidth(30);
colorBox.getChildren().addAll(colorCheckBox, colorRegion, colorLabel);
colorVBox.setPadding(new Insets(5,5,5,5));
colorVBox.getChildren().addAll(colorHeaderLabel,colorBox);
colorVBox.setAlignment(Pos.CENTER);
colorVBox.setBorder(blackBorder);
Håvard Daleng
committed
Region separator1 = new Region();
separator1.setMinHeight(10);
Region separator2 = new Region();
separator2.setMinHeight(10);
//Create spacing
Region space = new Region();
Region spacer = new Region();
space.setMinHeight(10);
spacer.setMinHeight(10);
parameterBox.getChildren().addAll(stepCountBox, spacer, coordinateControlBox);
parameterBox.setPadding(new Insets(10));
// Add basic control buttons
controlButtonBox.getChildren().addAll(canvasLabel,startButton, stopButton,clearButton);
controlButtonBox.setSpacing(5);
sideMenu.getChildren().add(controlButtonBox);
// Add spacing
sideMenu.getChildren().add(space);
sideMenu.getChildren().addAll(startButton,stopButton, clearButton);
//Radio Button header label
Label chaosGameTypeLabel = new Label("Chaos Game Selection");
chaosGameTypeLabel.setFont(new Font("Arial",20));
chaosGameTypeLabel.setAlignment(Pos.CENTER);
// Add fractal radio buttons
radioButtonBox.getChildren().addAll(chaosGameTypeLabel,sierpinskiRadioButton, barnsleyRadioButton, juliaRadioButton,
improvedBarnsleyButton);
radioButtonBox.setSpacing(5);
sideMenu.getChildren().add(radioButtonBox);
sideMenu.getChildren().addAll(separator1, colorVBox, separator2);
//this.initializeColorButtonHandler();
Håvard Daleng
committed
// Add parameter VBox
sideMenu.getChildren().add(parameterBox);
// Add file buttons and quit button
Label menuButtonLabel = new Label("Menu Controls");
menuButtonLabel.setFont(new Font("Arial",20));
menuButtonLabel.setAlignment(Pos.CENTER);
bottomButtonBox.getChildren().addAll(menuButtonLabel,loadFractalFromFileButton,writeFractalToFileButton,quitButton);
bottomButtonBox.setSpacing(5);
bottomButtonBox.setBorder(blackBorder);
bottomButtonBox.setAlignment(Pos.CENTER);
bottomButtonBox.setPadding(new Insets(5,5,5,5));
sideMenu.getChildren().add(bottomButtonBox);
// Add padding
sideMenu.setPadding(new Insets(10));
// Create split pane and button to toggle sidebar
this.sideMenuButton = new Button(">>");
Tooltip sideMenuButtonTooltip = new Tooltip("Hide/Unhide menu");
Tooltip.install(sideMenuButton, sideMenuButtonTooltip);
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: lightblue; -fx-background-radius: 5;");
this.borderPane = new BorderPane();
this.borderPane.setCenter(imageView);
this.borderPane.setRight(rightVBox);
rightVBox.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("<<");
}
});
}
Håvard Daleng
committed
/**
* Initialize the color button handler.
*/
private void initializeColorButtonHandler() {
this.colorCheckBox.setOnAction(event -> {
controller.game.setUseColor(colorCheckBox.isSelected());
Håvard Daleng
committed
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);
}
Håvard Daleng
committed
/**
* 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.
*/
Håvard Daleng
committed
@Override
Håvard Daleng
committed
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
controller.drawChaosGame();
}
public TextField getStepCountTextField() {
return this.stepCountTextField;
}
public CheckBox getColorCheckBox() {
return this.colorCheckBox;
}
/**
* Get the canvas for this GUI.
*
* @return the canvas.
*/
public Canvas getCanvas() {
return this.canvas;
public Button getStartButton() {
return this.startButton;
}
public Button getStopButton() {
return this.stopButton;
}
public Button getClearButton() {
return this.clearButton;
}
public Button getQuitButton() {
return this.quitButton;
}
public ButtonBase getWriteToFileButton() {
return this.writeFractalToFileButton;
}
public Window getStage() {
return this.primaryStage;
}
public RadioButton getSierpinskiRadioButton() {
return this.sierpinskiRadioButton;
}
public RadioButton getBarnsleyRadioButton() {
return this.barnsleyRadioButton;
}
public RadioButton getJuliaRadioButton() {
return this.juliaRadioButton;
}
public RadioButton getImprovedBarnsleyButton(){
return this.improvedBarnsleyButton;
}
private void resizeCanvas() {
double newWidth = scene.getWidth() - sideMenu.getWidth();
double newHeight = scene.getHeight();
if (newWidth / newHeight > aspectRatio) {
newWidth = newHeight * aspectRatio;
} else {
newHeight = newWidth / aspectRatio;
}
// Update imageView size to new calculated dimensions
imageView.setFitWidth(newWidth);
imageView.setFitHeight(newHeight);
// Redraw the fractal to fit the new canvas size
controller.drawChaosGame();
}