"...git@gitlab.stud.idi.ntnu.no:maikebre/devops-workshop.git" did not exist on "0665f21f45436c5b9ff02423e6534ba3215e3fe0"
Newer
Older
import edu.ntnu.stud.chaosgame.controller.game.ChaosGame;
import edu.ntnu.stud.chaosgame.controller.game.GuiButtonController;
Håvard Daleng
committed
import edu.ntnu.stud.chaosgame.controller.utility.Formatter;
Magnus Eik
committed
import edu.ntnu.stud.chaosgame.model.game.ChaosCanvas;
import edu.ntnu.stud.chaosgame.model.game.ChaosGameDescription;
import edu.ntnu.stud.chaosgame.model.generators.ChaosGameDescriptionFactory;
Magnus Eik
committed
import java.io.IOException;
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.util.Duration;
// todo: look through GUI and get rid of redundancies, clean up code
Magnus Eik
committed
public class ChaosGameGui implements ChaosGameObserver, GuiButtonObserver {
/** The primary stage for the GUI. */
Håvard Daleng
committed
private final Stage primaryStage;
Magnus Eik
committed
/** The aspect ratio of the GUI. */
Håvard Daleng
committed
private final double aspectRatio;
Magnus Eik
committed
private final int currentLine = 0;
/** The controller for the GUI. */
private final GuiButtonController controller;
/** The canvas for this GUI. */
private Canvas canvas;
Magnus Eik
committed
/** The ChaosCanvas for this GUI. */
private ChaosCanvas chaosCanvas;
Magnus Eik
committed
/** The ChaosGameDescription. */
Magnus Eik
committed
/** The ChaosGameDescriptionFactory. */
private ChaosGameDescriptionFactory factory;
Magnus Eik
committed
/** The ImageView for the GUI. */
private ChaosGameImageView imageView;
Magnus Eik
committed
/** The Scene for the GUI. */
Magnus Eik
committed
/** The width of the GUI. */
Magnus Eik
committed
/** The height of the GUI. */
Magnus Eik
committed
/** The ChaosGame for this GUI. */
Magnus Eik
committed
/** The Timeline for the GUI. */
Magnus Eik
committed
/** The BorderPane for the GUI. */
Magnus Eik
committed
/** The side menu for the GUI. */
Magnus Eik
committed
/** The start, stop, new, clear, quit and show sidebar buttons for the GUI. */
Magnus Eik
committed
/** The stop button for the GUI. */
Magnus Eik
committed
/** The button for clearing */
Magnus Eik
committed
/** The quit button for the GUI. */
Magnus Eik
committed
/** The side menu button for the GUI. */
private Button sideMenuButton;
Magnus Eik
committed
/** The load fractal from file and write fractal to file buttons for the GUI. */
Magnus Eik
committed
/** The write fractal to file button for the GUI. */
Magnus Eik
committed
/** The button which opens a menu to modify the game */
private Button modifyGameButton;
Magnus Eik
committed
/** A description ComboBox for choosing different fractal descriptions. */
Magnus Eik
committed
/** The step count text field for the GUI. */
Håvard Daleng
committed
private TextField stepCountTextField;
Magnus Eik
committed
/** The iteration limiter text field for the GUI */
private TextField iterationLimitTextField;
Magnus Eik
committed
/** The color check box for the GUI. */
Håvard Daleng
committed
private CheckBox colorCheckBox;
Magnus Eik
committed
/** Button to save an image of the fractal. */
private Button saveImageButton;
/**
* 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
Magnus Eik
committed
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
Magnus Eik
committed
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());
Magnus Eik
committed
/** Initialize the components of the GUI. */
private void initializeComponents() {
// Timeline
Magnus Eik
committed
// this.timeline = new Timeline(new KeyFrame(Duration.seconds(0.05), event ->
// controller.drawChaosGame()));
Magnus Eik
committed
// this.initializeMainButtons();
this.initializeFractalComponents();
Magnus Eik
committed
this.scene = new Scene(this.borderPane, 1700, 1000);
Magnus Eik
committed
/** 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;
}
Magnus Eik
committed
/** 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);
Magnus Eik
committed
this.chaosCanvas =
new ChaosCanvas(
1000, 1000, this.description.getMinCoords(), this.description.getMaxCoords());
game = new ChaosGame(this.description, chaosCanvas);
Håvard Daleng
committed
}
Magnus Eik
committed
/** 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);
canvas.widthProperty().bind(imageView.fitWidthProperty());
canvas.heightProperty().bind(imageView.fitHeightProperty());
Håvard Daleng
committed
this.clearImageView();
Magnus Eik
committed
/** 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);
Magnus Eik
committed
/** Initialize the buttons related to managing the fractals. */
private void initializeFractalComponents() {
this.loadFractalFromFileButton = new Button("Load Fractal");
Magnus Eik
committed
Tooltip loadFractalFromFileButtonTooltip =
new Tooltip("Load a text file describing a new fractal chaos game");
Tooltip.install(loadFractalFromFileButton, loadFractalFromFileButtonTooltip);
this.writeFractalToFileButton = new Button("Write to File");
Magnus Eik
committed
Tooltip writeFractalToFileButtonTooltip =
new Tooltip("Write a text file defining the current fractal chaos game to chosen location");
Tooltip.install(writeFractalToFileButton, writeFractalToFileButtonTooltip);
Magnus Eik
committed
/** Initialize the side menu for the GUI, including all its buttons and other components. */
Magnus Eik
committed
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);
Magnus Eik
committed
canvasLabel.setFont(new Font("Arial", 20));
this.startButton = new Button("Start");
this.quitButton = new Button("Quit");
Magnus Eik
committed
// this.sideMenuButton = new Button("Side Menu");
Magnus Eik
committed
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
Magnus Eik
committed
Tooltip.install(startButton, startButtonTooltip);
Tooltip.install(stopButton, stopButtonTooltip);
Tooltip.install(clearButton, clearButtonTooltip);
// Stylize Buttons
startButton.setStyle("-fx-background-color: #006400;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
stopButton.setStyle("-fx-background-color: #D2691E;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
clearButton.setStyle("-fx-background-color: #00008B;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
quitButton.setStyle("-fx-background-color: #980007;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
Magnus Eik
committed
// this.sideMenu.setAlignment(Pos.CENTER);
// Parameters
VBox parameterBox = new VBox();
VBox controlButtonBox = new VBox();
Magnus Eik
committed
controlButtonBox.setPadding(new Insets(5, 5, 5, 5));
VBox descriptionBox = new VBox();
descriptionBox.setBorder(blackBorder);
Magnus Eik
committed
descriptionBox.setPadding(new Insets(5, 5, 5, 5));
VBox bottomButtonBox = new VBox();
// Step Count GUI
VBox stepCountBox = new VBox();
Label stepCountLabel = new Label("Step Count");
Magnus Eik
committed
stepCountLabel.setFont(new Font("Arial", 20));
Label steppingSpeedLabel = new Label("Stepping Speed");
Håvard Daleng
committed
this.stepCountTextField = new TextField();
this.stepCountTextField.setTextFormatter(Formatter.getIntFormatter());
stepCountTextField.setPrefHeight(5);
stepCountTextField.setPrefWidth(50);
stepCountTextField.setText("1000");
Label iterationLimterLabel = new Label("Iteration Limit");
this.iterationLimitTextField = new TextField();
this.iterationLimitTextField.setTextFormatter(Formatter.getIntFormatter());
Magnus Eik
committed
Formatter.limitTextFieldSize(iterationLimitTextField, 4);
iterationLimitTextField.setText("500");
Magnus Eik
committed
stepCountBox
.getChildren()
.addAll(
stepCountLabel,
steppingSpeedLabel,
stepCountTextField,
iterationLimterLabel,
iterationLimitTextField);
Magnus Eik
committed
stepCountBox.setPadding(new Insets(5, 5, 5, 5));
stepCountBox.setBorder(blackBorder);
// Create a Box for Coordinate Controls
VBox modifyGameBox = new VBox();
modifyGameBox.setPadding(new Insets(5, 5, 5, 5));
Label coordinateHeader = new Label("Game Modification");
coordinateHeader.setFont(new Font("Arial", 20));
coordinateHeader.setAlignment(Pos.CENTER);
modifyGameBox.getChildren().add(coordinateHeader);
// Button for game modification popup
modifyGameButton = new Button("Create Modified Game");
Tooltip modifyGameButtonTooltip = new Tooltip("Create New Chaos Game From Current");
Magnus Eik
committed
Tooltip.install(modifyGameButton, modifyGameButtonTooltip);
modifyGameBox.getChildren().addAll(modifyGameButton);
modifyGameBox.setAlignment(Pos.CENTER);
modifyGameBox.setBorder(blackBorder);
Label colorHeaderLabel = new Label("Color Control");
Magnus Eik
committed
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();
Magnus Eik
committed
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);
Magnus Eik
committed
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);
Magnus Eik
committed
// Create spacing
Region space = new Region();
Region spacer = new Region();
space.setMinHeight(10);
spacer.setMinHeight(10);
parameterBox.getChildren().addAll(stepCountBox, spacer, modifyGameBox);
parameterBox.setPadding(new Insets(10));
// Add basic control buttons
Magnus Eik
committed
controlButtonBox.getChildren().addAll(canvasLabel, startButton, stopButton, clearButton);
controlButtonBox.setSpacing(5);
sideMenu.getChildren().add(controlButtonBox);
// Add spacing
sideMenu.getChildren().add(space);
Magnus Eik
committed
// Radio Button header label
Label chaosGameTypeLabel = new Label("Chaos Game Selection");
Magnus Eik
committed
chaosGameTypeLabel.setFont(new Font("Arial", 20));
chaosGameTypeLabel.setAlignment(Pos.CENTER);
// Add fractal radio buttons
Magnus Eik
committed
descriptionBox.getChildren().addAll(chaosGameTypeLabel, descriptionComboBox);
descriptionBox.setSpacing(5);
sideMenu.getChildren().add(descriptionBox);
sideMenu.getChildren().addAll(separator1, colorVBox, separator2);
Magnus Eik
committed
// this.initializeColorButtonHandler();
Håvard Daleng
committed
// Add parameter VBox
sideMenu.getChildren().add(parameterBox);
// Add file buttons and quit button
Magnus Eik
committed
menuButtonLabel.setFont(new Font("Arial", 20));
saveImageButton = new Button("Save Image");
Magnus Eik
committed
bottomButtonBox
.getChildren()
.addAll(
menuButtonLabel,
saveImageButton,
loadFractalFromFileButton,
writeFractalToFileButton,
quitButton);
bottomButtonBox.setSpacing(5);
bottomButtonBox.setBorder(blackBorder);
bottomButtonBox.setAlignment(Pos.CENTER);
Magnus Eik
committed
bottomButtonBox.setPadding(new Insets(5, 5, 5, 5));
sideMenu.getChildren().add(bottomButtonBox);
// Stylize buttons
modifyGameButton.setStyle("-fx-background-color: #00008B;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
loadFractalFromFileButton.setStyle("-fx-background-color: #00008B;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
saveImageButton.setStyle("-fx-background-color: #00008B;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
writeFractalToFileButton.setStyle("-fx-background-color: #00008B;"
+ " -fx-padding: 8 16; -fx-background-radius: 16; -fx-background-insets: 1px;"
+ " -fx-border-width: 2px; -fx-border-color: white; -fx-border-radius: 16;"
+ "-fx-font-size: 14; -fx-text-fill: white;");
// 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();
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);
borderPane.setFocusTraversable(false);
}
Magnus Eik
committed
* 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);
Magnus Eik
committed
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
/**
Håvard Daleng
committed
* Get the image view of this GUI.
*
Håvard Daleng
committed
* @return the image view.
*/
Magnus Eik
committed
public ImageView getImageView() {
Håvard Daleng
committed
/**
Magnus Eik
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.
Håvard Daleng
committed
*
* @param canvas the canvas to update with.
*/
Håvard Daleng
committed
@Override
Håvard Daleng
committed
public void updateCanvas(ChaosCanvas canvas) {
this.chaosCanvas = canvas;
}
Magnus Eik
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
Håvard Daleng
committed
Magnus Eik
committed
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
// GuiButtonObserver methods
@Override
public void onStartButtonPressed() {
controller.startGame();
}
@Override
public void onStopButtonPressed() {
controller.stopGame();
}
@Override
public void onClearButtonPressed() {
controller.clearCanvas();
}
@Override
public void onQuitButtonPressed() {
controller.quitGame();
}
@Override
public void onSaveImageButtonPressed() {
controller.saveImage();
}
@Override
public void onLoadFractalFromFileButtonPressed() {
controller.loadFractalFromFile();
}
@Override
public void onWriteToFileButtonPressed() {
controller.writeFractalToFile();
}
@Override
public void onModifyGameButtonPressed() {
controller.modifyGame();
}
Håvard Daleng
committed
/**
* Get the step count text field for this GUI.
*
* @return the step count text field.
*/
public TextField getStepCountTextField() {
return this.stepCountTextField;
}
Håvard Daleng
committed
/**
* Get the iteration limit text field for this GUI.
*
* @return the iteration limit text field.
*/
Magnus Eik
committed
public TextField getIterationLimitTextField() {
return this.iterationLimitTextField;
}
Håvard Daleng
committed
/**
* Get the color check box for this GUI.
*
* @return the color check box.
*/
public CheckBox getColorCheckBox() {
return this.colorCheckBox;
}
/**
* Get the canvas for this GUI.
*
* @return the canvas.
*/
public Canvas getCanvas() {
return this.canvas;
Håvard Daleng
committed
/**
* Get the start button for this GUI.
*
* @return the start button.
*/
public Button getStartButton() {
return this.startButton;
}
Håvard Daleng
committed
/**
* Get the stop button for this GUI.
*
* @return the stop button.
*/
public Button getStopButton() {
return this.stopButton;
}
Håvard Daleng
committed
/**
* Get the clear button for this GUI.
*
* @return the clear button.
*/
public Button getClearButton() {
return this.clearButton;
}
Håvard Daleng
committed
/**
* Get the quit button for this GUI.
*
* @return the quit button.
*/
public Button getQuitButton() {
return this.quitButton;
}
Håvard Daleng
committed
/**
* Get the write fractal to file button.
*
* @return the write fractal to file button.
*/
public Button getWriteToFileButton() {
return this.writeFractalToFileButton;
}
Håvard Daleng
committed
/**
* Get the primary stage for this GUI.
*
* @return the primary stage.
*/
public Window getStage() {
return this.primaryStage;
}
Magnus Eik
committed
/** Resize the canvas to fit the new dimensions of the scene. */
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();
}
Håvard Daleng
committed
/**
* Get the load fractal from file button.
*
* @return the load fractal from file button.
*/
public Button getLoadFractalFromFileButton() {
return this.loadFractalFromFileButton;
}
Håvard Daleng
committed
/**
* Get the modify game button.
*
* @return the modify game button.
*/
Magnus Eik
committed
public Button getModifyGameButton() {
return this.modifyGameButton;
}
Håvard Daleng
committed
/**
* Get the description combo box.
*
* @return the description combo box.
*/
Magnus Eik
committed
public ComboBox getDescriptionComboBox() {
return this.descriptionComboBox;
}
Håvard Daleng
committed
/**
* Get the save image button.
*
* @return the save image button.
*/
Magnus Eik
committed
public Button getSaveImageButton() {
return this.saveImageButton;
}