From 1671807bff67dea4a1bdf3851be5642446414fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Daleng?= <142524365+MrMarHVD@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:13:04 +0200 Subject: [PATCH] Implemented working zoom and pan functionality. --- .../java/edu/ntnu/stud/chaosgame/Main.java | 11 +-- .../chaosgame/controller/PopupManager.java | 21 +++++ ...GameGUIView.java => ChaosGameGuiView.java} | 35 ++++++-- .../chaosgame/view/ChaosGameImageView.java | 87 +++++++++++++++++++ 4 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 src/main/java/edu/ntnu/stud/chaosgame/controller/PopupManager.java rename src/main/java/edu/ntnu/stud/chaosgame/view/{ChaosGameGUIView.java => ChaosGameGuiView.java} (95%) create mode 100644 src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameImageView.java diff --git a/src/main/java/edu/ntnu/stud/chaosgame/Main.java b/src/main/java/edu/ntnu/stud/chaosgame/Main.java index 2019418..58d22a0 100644 --- a/src/main/java/edu/ntnu/stud/chaosgame/Main.java +++ b/src/main/java/edu/ntnu/stud/chaosgame/Main.java @@ -1,17 +1,10 @@ package edu.ntnu.stud.chaosgame; -import edu.ntnu.stud.chaosgame.controller.game.ChaosGameDescription; -import edu.ntnu.stud.chaosgame.controller.game.ChaosGameFileHandler; -import edu.ntnu.stud.chaosgame.controller.game.ChaosCanvas; -import edu.ntnu.stud.chaosgame.controller.game.ChaosGame; -import edu.ntnu.stud.chaosgame.model.generators.ChaosGameDescriptionFactory; -import edu.ntnu.stud.chaosgame.view.ChaosGameGUIView; +import edu.ntnu.stud.chaosgame.view.ChaosGameGuiView; import javafx.application.Application; import javafx.stage.Stage; import java.io.IOException; -import java.util.Objects; -import java.util.Scanner; /** * Main class for the Chaos Game application. @@ -20,7 +13,7 @@ public class Main extends Application { @Override public void start(Stage primaryStage) throws IOException { - ChaosGameGUIView view = new ChaosGameGUIView(primaryStage); + ChaosGameGuiView view = new ChaosGameGuiView(primaryStage); } diff --git a/src/main/java/edu/ntnu/stud/chaosgame/controller/PopupManager.java b/src/main/java/edu/ntnu/stud/chaosgame/controller/PopupManager.java new file mode 100644 index 0000000..5cec0d7 --- /dev/null +++ b/src/main/java/edu/ntnu/stud/chaosgame/controller/PopupManager.java @@ -0,0 +1,21 @@ +package edu.ntnu.stud.chaosgame.controller; + +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; + +public class PopupManager { + + /** + * Display a basic error. + * + * @param header the header of the popup message. + * @param content the content of the popup message. + */ + public static void displayError(String header, String content) { + Alert alert = new Alert(AlertType.ERROR); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + } + +} diff --git a/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGUIView.java b/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGuiView.java similarity index 95% rename from src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGUIView.java rename to src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGuiView.java index 7aae505..1777cdb 100644 --- a/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGUIView.java +++ b/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameGuiView.java @@ -10,7 +10,6 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.geometry.Insets; -import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.image.Image; @@ -26,7 +25,7 @@ import javafx.util.Duration; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; -public class ChaosGameGUIView implements ChaosGameObserver { +public class ChaosGameGuiView implements ChaosGameObserver { private int currentLine = 0; /** @@ -57,7 +56,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { /** * The ImageView for the GUI.. */ - private ImageView imageView; + private ChaosGameImageView imageView; /** * The Scene for the GUI.. @@ -114,7 +113,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { private RadioButton improvedBarnsleyButton; - public ChaosGameGUIView(Stage primaryStage) throws IOException { + public ChaosGameGuiView(Stage primaryStage) throws IOException { this.initializeComponents(); @@ -195,21 +194,22 @@ public class ChaosGameGUIView implements ChaosGameObserver { */ private void initializeImageView() { // Image view - this.imageView = new ImageView(); + this.imageView = new ChaosGameImageView(this); width = 1000; height = 1000; WritableImage writableImage = new WritableImage(width, height); pixelWriter = writableImage.getPixelWriter(); this.imageView.setImage(writableImage); - imageView.setOnScroll(event -> { + this.fillImageView(); + /*imageView.setOnScroll(event -> { double zoomFactor = 1.05; // This is the zoom factor per scroll - double deltaY = event.getDeltaY(); + double movement = event.getDeltaY(); imageView.setTranslateX(imageView.getTranslateX() - event.getDeltaX()); imageView.setTranslateY(imageView.getTranslateY() - event.getDeltaY()); - if (deltaY > 0) { + if (movement > 0) { imageView.setScaleX(imageView.getScaleX() * zoomFactor); imageView.setScaleY(imageView.getScaleY() * zoomFactor); } else { @@ -224,7 +224,20 @@ public class ChaosGameGUIView implements ChaosGameObserver { 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); + } + } } @@ -255,6 +268,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { 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 @@ -269,6 +283,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { 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(1000, 1000, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); @@ -282,6 +297,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { 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(1000, 1000, this.descriptionRef.get().getMinCoords(), this.descriptionRef.get().getMaxCoords())); @@ -294,6 +310,7 @@ public class ChaosGameGUIView implements ChaosGameObserver { WritableImage newWritableImage = new WritableImage(width, height); setPixelWriter(newWritableImage.getPixelWriter()); setImageViewFromImage(newWritableImage); + this.fillImageView(); canvas.clearCanvas(); // Test diff --git a/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameImageView.java b/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameImageView.java new file mode 100644 index 0000000..4020511 --- /dev/null +++ b/src/main/java/edu/ntnu/stud/chaosgame/view/ChaosGameImageView.java @@ -0,0 +1,87 @@ +package edu.ntnu.stud.chaosgame.view; + +import edu.ntnu.stud.chaosgame.controller.PopupManager; +import javafx.scene.image.ImageView; +import javafx.scene.input.ScrollEvent; +import javafx.scene.transform.Affine; + +/** + * This class extends ImageView to implement proper zooming and panning + * according to the requirements of the chaos game. + */ +public class ChaosGameImageView extends ImageView { + + private final ChaosGameGuiView controller; + private final Affine transform = new Affine(); + + private double lastCentreX; + private double lastCentreY; + private double startX; + private double startY; + + public ChaosGameImageView(ChaosGameGuiView controller) { + this.setOnScroll(this::zoom); + this.setOnMousePressed(this::mousePressed); + this.setOnMouseDragged(this::mouseDragged); + this.setOnMouseReleased(this::mouseReleased); + this.getTransforms().add(transform); + this.controller = controller; + this.lastCentreX = (float) controller.getWidth() / 2; + this.lastCentreY = (float) -controller.getHeight() / 2; + + //this.setStyle("-fx-background-color: white;"); + } + + /** + * Zooms the image view in or out based on the scroll event. + * + * @param event the event. + */ + private synchronized void zoom(ScrollEvent event) { + double zoomFactor = event.getDeltaY() > 0 ? 1.20 : 1 / 1.05; + try { + // Get the old values + double oldScaleX = transform.getMxx(); + double oldScaleY = transform.getMyy(); + double oldTranslateX = transform.getTx(); + double oldTranslateY = transform.getTy(); + + // Compute the new values + double newScaleX = oldScaleX * zoomFactor; + double newScaleY = oldScaleY * zoomFactor; + double newTranslateX = oldTranslateX - (event.getX() * (newScaleX - oldScaleX)); + double newTranslateY = oldTranslateY - (event.getY() * (newScaleY - oldScaleY)); + + // Update the transform + transform.setMxx(newScaleX); + transform.setMyy(newScaleY); + transform.setTx(newTranslateX); + transform.setTy(newTranslateY); + } catch (Exception e) { + PopupManager.displayError("Zoom error", e.getMessage()); + } + } + + private synchronized void mousePressed(javafx.scene.input.MouseEvent event) { + startX = event.getX(); + startY = event.getY(); + } + + private synchronized void mouseDragged(javafx.scene.input.MouseEvent event) { + double deltaX = event.getX() - startX; + double deltaY = event.getY() - startY; + + transform.setTx(transform.getTx() + deltaX); + transform.setTy(transform.getTy() + deltaY); + } + + private synchronized void mouseReleased(javafx.scene.input.MouseEvent event) { + double deltaX = event.getX() - startX; + double deltaY = event.getY() - startY; + + + lastCentreX += deltaX; + lastCentreY += deltaY; + } + +} -- GitLab