javajavafx

Create multiple circles on the same scene using JavaFX and setOnMouseMoved method


I started coding with JavaFX a couple of days ago and this is one exercise who's been bothering me for the past five hours or so. I want to add circles to the scene by first clicking where I want the center to be and then moving the cursor to get the radius; also I'm forcing myself not to use Canvas for the time being.

The code below was slightly modified from the one available here:

Draw circle with mouse click points (JavaFX)

in order to leave every drawn circle on the screen.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class TestJavaFX extends Application {

    private double cX, cY;
    public boolean firstClick = true;

    @Override

    public void start(Stage primaryStage) {
        Group root = new Group();
        Scene scene = new Scene(root, 960, 540);

        scene.setOnMouseClicked(evt -> {

            if (firstClick) {
                cX = evt.getX();
                cY = evt.getY();
                firstClick = false;
            } else {
                double r = Math.sqrt(Math.pow(cX - evt.getX(), 2) + Math.pow(cY - evt.getY(), 2));
                Circle circle = new Circle(cX, cY, r, Color.BLUE);
                root.getChildren().add(circle);
                firstClick = true;
            }
        });

        primaryStage.setTitle("TestJavaFX");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

I've come up with the code above to add circles to the scene by clicking twice but I was not able to replicate the same result using setOnMouseMoved. Putting Circle circle = new Circle() inside a setOnMouseMoved event creates a new circle at every movement of the cursor effectively making impossible to interact with the screen.

---------- Update based on @James_D's suggestion ----------

Despite being a wonderful suggestion and the sequence feeling way more natural, a new circle is being added to root.getChildren() even if a single click is performed without actually dragging the mouse. In other words root is being populated also by circles having radius equal to zero, created from a user's erroneous click. You can see what I mean in the image below where I simply added a System.out.println(root.getChildren().size()) to the first event.

#CirclesDisplayed vs. #CirclesAddedToRoot


Solution

  • Other answers have provided implementations of the functionality as described. For completeness, here is an implementation of the alternative I suggested using press-drag-release as the mouse gesture:

    import javafx.application.Application;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;
    
    public class TestJavaFX extends Application {
    
    
        private Circle currentCircle;
    
        @Override
    
        public void start(Stage primaryStage) {
            Group root = new Group();
            Scene scene = new Scene(root, 960, 540);
    
            scene.setOnMousePressed(evt -> {
                    currentCircle = new Circle(evt.getX(), evt.getY(), 0, Color.BLUE);
                    root.getChildren().add(currentCircle);
            });
    
            scene.setOnMouseDragged(evt -> {
                double cx = currentCircle.getCenterX();
                double cy = currentCircle.getCenterY();
                double edgeX = evt.getX();
                double edgeY = evt.getY();
                double deltaX = cx - edgeX;
                double deltaY = cy - edgeY;
                double r = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                currentCircle.setRadius(r);
            });
    
            scene.setOnMouseReleased( _ -> {
                if (currentCircle.getRadius() < 1) {
                    root.getChildren().remove(currentCircle);
                }
                currentCircle = null;
            });
    
            primaryStage.setTitle("TestJavaFX");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    }
    

    Note that in this case the code is simpler, in that there is no testing needed to see what state the drawing process is in. That state is inherent in the semantics of the different types of mouse event (press to start a new circle, drag to change the radius, release to stop drawing). There's also less state to track: the only instance variable is the "current circle".

    In the onMouseReleased handler here I removed the current circle if the radius is less than one pixel. This allows the user to "change their mind" and not add the circle after clicking, by releasing the mouse without dragging it. (Thanks to @SedJ601 for the suugestion.) Ultimately, it might be good to implement some "undo" (e.g. a key pressed handler on the scene handling shortcut+z to remove the last circle) or "remove" (e.g. right-click on the circle to remove it) functionality. Neither of these are difficult to implement, but are, as is commonly said in text books, left as exercises for the reader.