From 88e3ee10a14f7d4624a5b0e4b5ca0784d1970dd2 Mon Sep 17 00:00:00 2001
From: Edvard <edvardee@stud.ntnu.no>
Date: Fri, 12 Apr 2024 19:16:13 +0200
Subject: [PATCH] Add open from file button in ChaosGame

Add button that when pressed opens a FileChooser that filters through and
only allows to open .txt files. The game is updated if a correctly formatted
text file is selected. In addition, the drag and move event in ExplorePage
is improved to not render through entire drag movement. Now the image only renders
when the user releases the image.
---
 .../java/org/example/chaosgame/MainApp.java   |   3 -
 ...ntroller.java => ChaosGameController.java} |  13 +-
 .../{ChaosGameSubject.java => Subject.java}   |   2 +-
 .../chaosgame/model/chaos/ChaosGame.java      |   4 +-
 .../model/chaos/ChaosGameDescription.java     |  11 +-
 .../chaosgame/model/chaos/ExploreGame.java    |  18 +--
 .../org/example/chaosgame/view/ChaosPage.java |  50 ++++--
 .../example/chaosgame/view/ExplorePage.java   | 148 ++++++++++++++----
 8 files changed, 182 insertions(+), 67 deletions(-)
 rename src/main/java/org/example/chaosgame/controller/{MainController.java => ChaosGameController.java} (55%)
 rename src/main/java/org/example/chaosgame/controller/{ChaosGameSubject.java => Subject.java} (81%)

diff --git a/src/main/java/org/example/chaosgame/MainApp.java b/src/main/java/org/example/chaosgame/MainApp.java
index 20aa139..91e6367 100644
--- a/src/main/java/org/example/chaosgame/MainApp.java
+++ b/src/main/java/org/example/chaosgame/MainApp.java
@@ -6,10 +6,7 @@ import javafx.scene.control.Button;
 import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.HBox;
 import javafx.stage.Stage;
-import org.example.chaosgame.controller.MainController;
-import org.example.chaosgame.model.chaos.ChaosGame;
 import org.example.chaosgame.model.chaos.ChaosGameDescription;
-import org.example.chaosgame.model.chaos.ChaosGameDescriptionFactory;
 import org.example.chaosgame.model.chaos.ChaosGameFileHandler;
 import org.example.chaosgame.view.ChaosPage;
 import org.example.chaosgame.view.ExplorePage;
diff --git a/src/main/java/org/example/chaosgame/controller/MainController.java b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java
similarity index 55%
rename from src/main/java/org/example/chaosgame/controller/MainController.java
rename to src/main/java/org/example/chaosgame/controller/ChaosGameController.java
index fe97afa..e47d5e1 100644
--- a/src/main/java/org/example/chaosgame/controller/MainController.java
+++ b/src/main/java/org/example/chaosgame/controller/ChaosGameController.java
@@ -1,22 +1,23 @@
 package org.example.chaosgame.controller;
 
-import javafx.scene.layout.StackPane;
 import org.example.chaosgame.model.chaos.ChaosGame;
 import org.example.chaosgame.view.ChaosPage;
 
-public class MainController implements Observer{
+public class ChaosGameController implements Observer{
   private final ChaosGame chaosGame;
-  private final ChaosPage chaosPage;
+//  private final ChaosPage chaosPage;
 
 
-  public MainController(ChaosGame chaosGame) {
+
+
+  public ChaosGameController(ChaosGame chaosGame) {
     this.chaosGame = chaosGame;
-    this.chaosPage = new ChaosPage();
     chaosGame.registerObserver(this);
   }
 
   @Override
   public void update() {
-    chaosPage.updateCanvas();
+//    chaosPage.updateCanvas();
+    System.out.println("ChaosGameController.update");
   }
 }
diff --git a/src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java b/src/main/java/org/example/chaosgame/controller/Subject.java
similarity index 81%
rename from src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java
rename to src/main/java/org/example/chaosgame/controller/Subject.java
index 36dd862..79bd695 100644
--- a/src/main/java/org/example/chaosgame/controller/ChaosGameSubject.java
+++ b/src/main/java/org/example/chaosgame/controller/Subject.java
@@ -1,6 +1,6 @@
 package org.example.chaosgame.controller;
 
-public interface ChaosGameSubject {
+public interface Subject {
   void registerObserver(Observer observer);
   void removeObserver(Observer observer);
   void notifyObservers();
diff --git a/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java b/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java
index 88ec0ec..1910102 100644
--- a/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java
+++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosGame.java
@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
-import org.example.chaosgame.controller.ChaosGameSubject;
+import org.example.chaosgame.controller.Subject;
 import org.example.chaosgame.controller.Observer;
 import org.example.chaosgame.model.linalg.Vector2D;
 
@@ -17,7 +17,7 @@ import org.example.chaosgame.model.linalg.Vector2D;
  * The new point is then drawn on the canvas.
  * This process is repeated a selected amount of steps.
  */
-public class ChaosGame implements ChaosGameSubject {
+public class ChaosGame implements Subject {
   private final ChaosCanvas canvas;
 
   private final ChaosGameDescription description;
diff --git a/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java b/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java
index ca773de..107dd43 100644
--- a/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java
+++ b/src/main/java/org/example/chaosgame/model/chaos/ChaosGameDescription.java
@@ -11,8 +11,8 @@ import org.example.chaosgame.model.transformations.Transform2D;
  * and a list of transformations to apply to the points.
  */
 public class ChaosGameDescription {
-  private final Vector2D minCoords;
-  private final Vector2D maxCoords;
+  private Vector2D minCoords;
+  private Vector2D maxCoords;
   private final List<Transform2D> transforms;
 
   private final List<Integer> probabilities;
@@ -50,6 +50,13 @@ public class ChaosGameDescription {
     return maxCoords;
   }
 
+  public void setMinCoords(Vector2D minCoords) {
+    this.minCoords = minCoords;
+  }
+  public void setMaxCoords(Vector2D maxCoords) {
+    this.maxCoords = maxCoords;
+  }
+
   public List<Transform2D> getTransforms() {
     return transforms;
   }
diff --git a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java
index b0d356a..1803a80 100644
--- a/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java
+++ b/src/main/java/org/example/chaosgame/model/chaos/ExploreGame.java
@@ -53,23 +53,7 @@ public class ExploreGame{
 
       }
     });
-//    for (int y = 0; y < canvas.getHeight(); y++) {
-//      for (int x = 0; x < canvas.getWidth(); x++) {
-//        int iter = 0;
-//        currentPoint = canvas.transformIndicesToCoords(x, y);
-//        Vector2D tempPoint = currentPoint;
-//        while (iter < MAX_ITER && tempPoint.lengthSQ() < 4){
-//          tempPoint = description.getTransforms().getFirst().transform(tempPoint);
-//          iter++;
-//        }
-//        double abs = Math.sqrt(tempPoint.lengthSQ());
-//        double smooth = iter - Math.log(Math.log(abs)) / Math.log(2);
-//
-//        canvas.putPixel(x, y, smooth);
-//
-//      }
-//
-//    }
+
     long end = System.currentTimeMillis();
     System.out.println("Time taken: " + (end - start) + "ms");
   }
diff --git a/src/main/java/org/example/chaosgame/view/ChaosPage.java b/src/main/java/org/example/chaosgame/view/ChaosPage.java
index 658dfd3..3fdcf47 100644
--- a/src/main/java/org/example/chaosgame/view/ChaosPage.java
+++ b/src/main/java/org/example/chaosgame/view/ChaosPage.java
@@ -12,28 +12,34 @@ import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.StackPane;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
-import org.example.chaosgame.model.chaos.ChaosCanvas;
-import org.example.chaosgame.model.chaos.ChaosGame;
-import org.example.chaosgame.model.chaos.ChaosGameDescriptionFactory;
+import org.example.chaosgame.controller.ChaosGameController;
+import org.example.chaosgame.model.chaos.*;
 import org.example.chaosgame.model.linalg.Complex;
+import java.io.File;
+import java.io.IOException;
+import javafx.stage.FileChooser;
+import java.io.BufferedReader;
+import java.io.FileReader;
 
 public class ChaosPage {
+  ChaosGameController chaosGameController;
   private final StackPane chaosContent;
   private ChaosGame chaosGame;
   private ChaosCanvas chaosCanvas;
   private Complex c = new Complex(-0.70176, -0.3842);
   private final Button runStepsButton = new Button("Run Steps");
-  private final Canvas canvas;
-  private final GraphicsContext gc;
+  private final Canvas canvas = new Canvas(1200, 800);
+  private final GraphicsContext gc ;
   private final Label errorLabel = new Label("Invalid input. Please enter a valid number.");
   private final VBox runStepsBox = new VBox();
 
   public ChaosPage() {
+
     chaosContent = new StackPane();
+    gc = canvas.getGraphicsContext2D();
     updateChaosGame("Julia");
     chaosCanvas = chaosGame.getCanvas();
-    canvas = new Canvas(chaosCanvas.getWidth(), chaosCanvas.getHeight());
-    gc = canvas.getGraphicsContext2D();
+
 
 
     TextField stepsField = new TextField();
@@ -54,9 +60,9 @@ public class ChaosPage {
       } else {
         updateChaosGame(selectedGame);
       }
-      gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
       updateCanvas();
     });
+
     runStepsButton.setOnAction(e5 -> {
               if (!stepsField.getText().isEmpty()) {
                 try {
@@ -71,7 +77,25 @@ public class ChaosPage {
               }
             });
 
-    runStepsBox.getChildren().addAll(contextMenu,stepsField, runStepsButton);
+
+Button openFileButton = new Button("Open File");
+openFileButton.setOnAction(e -> {
+    FileChooser fileChooser = new FileChooser();
+    fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt"));
+    File selectedFile = fileChooser.showOpenDialog(null);
+
+    if (selectedFile != null) {
+        try {
+          ChaosGameFileHandler fileHandler = new ChaosGameFileHandler();
+          ChaosGameDescription description = fileHandler.readFromFile(selectedFile.getAbsolutePath());
+          updateChaosGame(description);
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+    }
+});
+
+    runStepsBox.getChildren().addAll(contextMenu,stepsField, runStepsButton, openFileButton);
     runStepsBox.setSpacing(10);
     runStepsBox.setPadding(new Insets(10));
     runStepsBox.setAlignment(Pos.CENTER_RIGHT);
@@ -113,6 +137,14 @@ public class ChaosPage {
 
   private void updateChaosGame(String chaosGameType) {
     chaosGame = new ChaosGame(ChaosGameDescriptionFactory.get(chaosGameType, c), 1200, 800);
+    chaosGameController = new ChaosGameController(chaosGame);
+    chaosCanvas = chaosGame.getCanvas();
+    gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
+  }
+  private void updateChaosGame(ChaosGameDescription description){
+    chaosGame = new ChaosGame(description, 1200, 800);
+    chaosGameController = new ChaosGameController(chaosGame);
     chaosCanvas = chaosGame.getCanvas();
+    gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
   }
 }
diff --git a/src/main/java/org/example/chaosgame/view/ExplorePage.java b/src/main/java/org/example/chaosgame/view/ExplorePage.java
index e548359..3aae4b4 100644
--- a/src/main/java/org/example/chaosgame/view/ExplorePage.java
+++ b/src/main/java/org/example/chaosgame/view/ExplorePage.java
@@ -1,5 +1,7 @@
 package org.example.chaosgame.view;
 
+import javafx.animation.AnimationTimer;
+import javafx.application.Platform;
 import javafx.geometry.Pos;
 import javafx.scene.canvas.Canvas;
 import javafx.scene.canvas.GraphicsContext;
@@ -27,6 +29,9 @@ public class ExplorePage {
   private Complex c = new Complex(-0.835, 0.2321);
   private final Canvas canvas;
   private final GraphicsContext gc;
+  private Vector2D dragStart;
+  private Vector2D dragStartTemp;
+
   private final List<Transform2D> trans = List.of(
                   new ExploreJulia(c)
           );
@@ -42,6 +47,7 @@ public class ExplorePage {
 
   private WritableImage offScreenImage;
   private PixelWriter pixelWriter;
+  private Vector2D dragDistance;
 
 
   public ExplorePage() {
@@ -71,27 +77,55 @@ public class ExplorePage {
 
 
     zoomInButton.setOnMousePressed(event -> {
-      double scaleFactor = 1.05;
-      Vector2D newMinCoords = description.getMinCoords().scale(1 / scaleFactor);
-      Vector2D newMaxCoords = description.getMaxCoords().scale(1 / scaleFactor);
-        description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans);
-        exploreGame = new ExploreGame(description, 1200, 800);
-//
-        exploreGame.exploreFractals();
-        updateCanvas();
+      AnimationTimer zoomInTimer = new AnimationTimer() {
+        @Override
+        public void handle(long now) {
+          double scaleFactor = 1.05;
+          Vector2D canvasCenter = new Vector2D(canvas.getWidth() / 2, canvas.getHeight() / 2);
+          Vector2D fractalCenter = chaosCanvas.transformIndicesToCoords((int)canvasCenter.getX(), (int)canvasCenter.getY());
+          Vector2D newMinCoords = fractalCenter.subtract(fractalCenter.subtract(description.getMinCoords()).scale(1 / scaleFactor));
+          Vector2D newMaxCoords = fractalCenter.add(description.getMaxCoords().subtract(fractalCenter).scale(1 / scaleFactor));
+          description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans);
+          exploreGame = new ExploreGame(description, 1200, 800);
+          exploreGame.exploreFractals();
+          updateCanvas();
+        }
+      };
+      zoomInTimer.start();
+      zoomInButton.setUserData(zoomInTimer); // Store the timer in the button's user data
+    });
 
+    zoomInButton.setOnMouseReleased(event -> {
+      AnimationTimer zoomInTimer = (AnimationTimer) zoomInButton.getUserData();
+      if (zoomInTimer != null) {
+        zoomInTimer.stop();
+      }
     });
 
-    zoomOutButton.setOnAction(event -> {
-      double scaleFactor = 1 / 1.05;
-      Vector2D newMinCoords = description.getMinCoords().scale(1 / scaleFactor);
-      Vector2D newMaxCoords = description.getMaxCoords().scale(1 / scaleFactor);
+    zoomOutButton.setOnMousePressed(event -> {
+      AnimationTimer zoomOutTimer = new AnimationTimer() {
+        @Override
+        public void handle(long now) {
+          double scaleFactor = 1.0 / 1.05;
+          Vector2D canvasCenter = new Vector2D(canvas.getWidth() / 2, canvas.getHeight() / 2);
+          Vector2D fractalCenter = chaosCanvas.transformIndicesToCoords((int)canvasCenter.getX(), (int)canvasCenter.getY());
+          Vector2D newMinCoords = fractalCenter.subtract(fractalCenter.subtract(description.getMinCoords()).scale(1 / scaleFactor));
+          Vector2D newMaxCoords = fractalCenter.add(description.getMaxCoords().subtract(fractalCenter).scale(1 / scaleFactor));
           description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans);
           exploreGame = new ExploreGame(description, 1200, 800);
-//          chaosCanvas = exploreGame.getCanvas();
           exploreGame.exploreFractals();
           updateCanvas();
+        }
+      };
+      zoomOutTimer.start();
+      zoomOutButton.setUserData(zoomOutTimer); // Store the timer in the button's user data
+    });
 
+    zoomOutButton.setOnMouseReleased(event -> {
+      AnimationTimer zoomOutTimer = (AnimationTimer) zoomOutButton.getUserData();
+      if (zoomOutTimer != null) {
+        zoomOutTimer.stop();
+      }
     });
 
 
@@ -102,35 +136,94 @@ public class ExplorePage {
 
     exploreContent.getChildren().addAll(canvas, buttons);
 
+    Platform.runLater(() -> {
+      buttons.setLayoutX(canvas.getWidth() - buttons.getWidth());
+      buttons.setLayoutY(0);
+    });
+
     exploreContent.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
       try {
+        dragStart = new Vector2D(event.getX(), event.getY());
+
         double mouseX = event.getX() - canvas.getLayoutX();
         double mouseY = event.getY() - canvas.getLayoutY();
-        initialMousePosition = new Vector2D(mouseX, canvas.getHeight() - mouseY);
+        dragStartTemp = new Vector2D(mouseX, canvas.getHeight() - mouseY);
+//        initialMousePosition = new Vector2D(mouseX, canvas.getHeight() - mouseY);
       } catch (Exception e) {
         e.printStackTrace();
     }
 });
 
 exploreContent.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
-    Vector2D currentMousePosition = new Vector2D(event.getX(),canvas.getHeight() - event.getY());
-    Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition);
+//    Vector2D currentMousePosition = new Vector2D(event.getX(),canvas.getHeight() - event.getY());
+//    Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition);
+//
+//    // Adjust the drag distance based on the zoom level
+//// Convert the drag distance from canvas coordinates to fractal coordinates
+//  Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords());
+//  Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight()));
+//
+//    Vector2D newMinCoords = description.getMinCoords().subtract(adjustedDragDistance);
+//    Vector2D newMaxCoords = description.getMaxCoords().subtract(adjustedDragDistance);
+//    description.setMinCoords(newMinCoords);
+//    description.setMaxCoords(newMaxCoords);
+//    exploreGame = new ExploreGame(description, 1200, 800);
+//    exploreGame.exploreFractals();
+//    updateCanvas();
+//
+//    initialMousePosition = currentMousePosition;
+
+  Vector2D dragEnd = new Vector2D(event.getX(), event.getY());
+  dragDistance = dragEnd.subtract(dragStart);
+
+  canvas.setTranslateX(canvas.getTranslateX() + dragDistance.getX());
+  canvas.setTranslateY(canvas.getTranslateY() + dragDistance.getY());
+
+  dragStart = dragEnd;
+
 
-    // Adjust the drag distance based on the zoom level
-// Convert the drag distance from canvas coordinates to fractal coordinates
-  Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords());
-  Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight()));
 
+
+});
+
+    exploreContent.setOnMouseReleased(event -> {
+      dragDistance = new Vector2D(event.getX(), canvas.getHeight() - event.getY()).subtract(dragStartTemp);
+//       Reset the position where the drag started
+      Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords());
+      System.out.println("drag distance: " + dragDistance.getX() + ", " + dragDistance.getY());
+    Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight()));
+      System.out.println("Adjusted drag distance: " + adjustedDragDistance.getX() + ", " + adjustedDragDistance.getY());
     Vector2D newMinCoords = description.getMinCoords().subtract(adjustedDragDistance);
     Vector2D newMaxCoords = description.getMaxCoords().subtract(adjustedDragDistance);
-
-    description = new ChaosGameDescription(newMinCoords, newMaxCoords, trans);
+    description.setMinCoords(newMinCoords);
+    description.setMaxCoords(newMaxCoords);
     exploreGame = new ExploreGame(description, 1200, 800);
     exploreGame.exploreFractals();
     updateCanvas();
+      System.out.println("Mouse released");
 
-    initialMousePosition = currentMousePosition;
-});
+      dragStart = null;
+      canvas.setTranslateX(0);
+      canvas.setTranslateY(0);
+    });
+
+//exploreContent.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
+//    Vector2D currentMousePosition = new Vector2D(event.getX(), canvas.getHeight() - event.getY());
+//    Vector2D dragDistance = currentMousePosition.subtract(initialMousePosition);
+//
+//    // Adjust the drag distance based on the zoom level
+//    Vector2D fractalRange = description.getMaxCoords().subtract(description.getMinCoords());
+//    Vector2D adjustedDragDistance = dragDistance.multiply(fractalRange).divide(new Vector2D(canvas.getWidth(), canvas.getHeight()));
+//
+//    Vector2D newMinCoords = description.getMinCoords().subtract(adjustedDragDistance);
+//    Vector2D newMaxCoords = description.getMaxCoords().subtract(adjustedDragDistance);
+//
+//    description.setMinCoords(newMinCoords);
+//    description.setMaxCoords(newMaxCoords);
+//    exploreGame.setDescription(description);
+//    exploreGame.exploreFractals();
+//    updateCanvas();
+//  });
   }
 
   public StackPane getExploreContent() {
@@ -152,9 +245,10 @@ exploreContent.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
     for (int i = 0; i < chaosCanvas.getHeight(); i++) {
       for (int j = 0; j < chaosCanvas.getWidth(); j++) {
         double color = Math.min(canvasArray[i][j] * 3, 255);
-        if (color > 0 && color < 255) {
-          pixelWriter.setColor(j, i, Color.rgb((int) color, (int) color, (int) color));
-        }
+//        if (color >= 0 && color <= 255) {
+          pixelWriter.setColor(j, i, Color.rgb((int) color, 0, 0));
+//        }
+
       }
     }
 
-- 
GitLab