Java Arena
Step 2 of 7

Drawing with the mouse

A canvas can react to the mouse, as a button reacts to clicks. That turns it into something to draw on.

Mouse events

A node has four methods for the mouse, each taking an event handler, usually a lambda as in module 49:

method its handler runs when
setOnMousePressed a mouse button goes down on the node
setOnMouseDragged the mouse moves while its button is down
setOnMouseReleased the button comes up again
setOnMouseClicked the button went down and came up on the node: a click

The handler gets a MouseEvent (from javafx.scene.input), and event.getX() and event.getY() say where the mouse was, in the canvas's own coordinates: (0, 0) is the canvas's top left corner, wherever the canvas is in the window. Both are doubles. A test pad that prints every event:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(200, 120);
        canvas.setOnMousePressed(event -> System.out.println("pressed at (" + event.getX() + ", " + event.getY() + ")"));
        canvas.setOnMouseDragged(event -> System.out.println("dragged to (" + event.getX() + ", " + event.getY() + ")"));
        canvas.setOnMouseReleased(event -> System.out.println("released at (" + event.getX() + ", " + event.getY() + ")"));
        canvas.setOnMouseClicked(event -> System.out.println("clicked at (" + event.getX() + ", " + event.getY() + ")"));

        stage.setScene(new Scene(new Pane(canvas)));
        stage.setTitle("Test pad");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

After: Click canvas 1 at (40, 30), press the mouse on canvas 1 at (100, 60), drag the mouse on canvas 1 to (120, 70), drag the mouse on canvas 1 to (140, 80), then release the mouse on canvas 1 at (140, 80)

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Test pad"
  Pane
    Canvas 200 x 120
Output
pressed at (40.0, 30.0)
released at (40.0, 30.0)
clicked at (40.0, 30.0)
pressed at (100.0, 60.0)
dragged to (120.0, 70.0)
dragged to (140.0, 80.0)
released at (140.0, 80.0)
clicked at (140.0, 80.0)

The event lines work like part 13's clicks and typing, with a point added. click Canvas 1 at 40 30 clicks the first canvas of the window at (40, 30). press, drag ... to and release move the mouse one step at a time: here it's pressed at (100, 60), moved with its button down to (120, 70) and then (140, 80), and let go there.

  • A click is three events, in this order: pressed, released, clicked.
  • A drag runs the dragged handler once for each point the mouse moves to. On a real screen that's every few pixels, so a handler for drags runs often.
  • After the drag, JavaFX still counts a click: the button went down and came up on the same node. A program that draws on clicks and on drags must keep that in mind.

Drawing where the mouse is

A game's level editor plants a flower in a meadow at each click: a gold circle 16 pixels across with its center at the mouse, and a small brown center in it. start draws the grass first:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(200, 120);
        GraphicsContext pen = canvas.getGraphicsContext2D();
        pen.setFill(Color.YELLOWGREEN);
        pen.fillRect(0, 0, 200, 120);
        canvas.setOnMouseClicked(event -> {
            double x = event.getX();
            double y = event.getY();
            pen.setFill(Color.GOLD);
            pen.fillOval(x - 8, y - 8, 16, 16);
            pen.setFill(Color.SADDLEBROWN);
            pen.fillOval(x - 3, y - 3, 6, 6);
        });

        stage.setScene(new Scene(new Pane(canvas)));
        stage.setTitle("Meadow");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

After: Click canvas 1 at (40, 30), click canvas 1 at (100, 80), then click canvas 1 at (160, 45)

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Meadow"
  Pane
    Canvas 200 x 120
      fillRect 0 0 200 120, fill #9acd32
      fillOval 32 22 16 16, fill #ffd700
      fillOval 37 27 6 6, fill #8b4513
      fillOval 92 72 16 16, fill #ffd700
      fillOval 97 77 6 6, fill #8b4513
      fillOval 152 37 16 16, fill #ffd700
      fillOval 157 42 6 6, fill #8b4513

The handler draws on the same pen that start used: a canvas keeps everything drawn on it, so each flower is added to the grass and the flowers before it. To center a circle 16 across on the mouse, its square starts 8 pixels to the left of the mouse and 8 above it. In the window as text, the grass's call comes first, then two calls for each click, in the order of the clicks.

Lines that follow the mouse

To draw a line as the mouse moves, the program joins each point to the one before it with a short line. So it has to remember the last point, from one event to the next. The handlers are lambdas, which can't change a local variable of start (module 49), so the point goes in two instance variables. A doodle pad, with buttons for the color and one to wipe it clean:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {
    private double lastX;
    private double lastY;

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(240, 140);
        GraphicsContext pen = canvas.getGraphicsContext2D();
        pen.setLineWidth(3);
        canvas.setOnMousePressed(event -> {
            this.lastX = event.getX();
            this.lastY = event.getY();
        });
        canvas.setOnMouseDragged(event -> {
            pen.strokeLine(this.lastX, this.lastY, event.getX(), event.getY());
            this.lastX = event.getX();
            this.lastY = event.getY();
        });

        Button black = new Button("Black");
        black.setOnAction(event -> pen.setStroke(Color.BLACK));
        Button red = new Button("Red");
        red.setOnAction(event -> pen.setStroke(Color.RED));
        Button clear = new Button("Clear");
        clear.setOnAction(event -> pen.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()));

        stage.setScene(new Scene(new VBox(new HBox(5, black, red, clear), canvas)));
        stage.setTitle("Doodle pad");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

After: Press the mouse on canvas 1 at (20, 100), drag the mouse on canvas 1 to (60, 40), drag the mouse on canvas 1 to (100, 100), release the mouse on canvas 1 at (100, 100), click "Red", press the mouse on canvas 1 at (140, 70), drag the mouse on canvas 1 to (180, 40), drag the mouse on canvas 1 to (220, 70), then release the mouse on canvas 1 at (220, 70)

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Doodle pad"
  VBox
    HBox spacing 5
      Button "Black"
      Button "Red"
      Button "Clear"
    Canvas 240 x 140
      strokeLine 20 100 60 40, stroke #000000, width 3
      strokeLine 60 40 100 100, stroke #000000, width 3
      strokeLine 140 70 180 40, stroke #ff0000, width 3
      strokeLine 180 40 220 70, stroke #ff0000, width 3
  • Pressing only remembers where the line starts. Each drag draws from the remembered point to the new one, and then remembers the new one, so the short lines join up.
  • The color buttons just call pen.setStroke(...). The GraphicsContext keeps its stroke color, so every line drawn after a click on Red is red, until another button changes it.
  • clearRect(x, y, width, height) wipes a rectangle clean: the canvas there is empty again, as at the start (the window shows through). With the canvas's own size, it wipes everything. In the window as text, a wiped canvas still lists what was drawn before it, followed by the clearRect line: the list is the canvas's whole history, in order.

Drawing yourself

The windows drawn in a lesson show the events listed with them, and can't be clicked. When you run a challenge's code with "Run with my input", or a program in the Playground, you can use its window: on a canvas that has mouse handlers, draw with the mouse or a finger. The page collects the whole movement (the press, a drag every few pixels and the release) and runs your program once with all of it, so the drawing updates when you lift your finger. A tap without moving is a click. With the keyboard, the arrow keys move a point, Enter clicks there, and Space presses there and, after the arrow keys have moved the point, lets go.

Your task

Challenge 1 of 3

A park ranger marks on a map where birds have built their nests. The starter code makes a canvas of 200 x 150 pixels with the label Nests: 0 under it. Make each click on the canvas mark a nest: an orange (Color.ORANGE) filled circle 12 pixels across, with its center where the mouse was clicked. The label then says how many nests are marked: Nests: 2 after two clicks. The window after the two clicks listed below is drawn under them.

Challenge 2

A delivery app asks for a signature. Under the label Sign below, in a VBox with no spacing, a canvas of 240 x 100 pixels takes it: pressing the mouse starts a line, and each drag draws a line from the point before to the new point, in dark blue (Color.DARKBLUE) and 2 pixels wide. Releasing draws nothing. Nothing else is drawn. The window after the events below is drawn under them.

Challenge 3

A room planner draws furniture as rectangles. Drag on the canvas from one corner of a piece to the opposite corner: where the mouse is pressed is one corner, and where it's released is the other. On release, draw the rectangle between them: first filled in light steel blue (Color.LIGHTSTEELBLUE), then its outline with the same numbers in the pen's own stroke (black, 1 pixel wide). Its top left corner is at the smaller x and the smaller y of the two points, and its width and height are the distances between them, so a drag up or to the left works too. When the width or the height is 0 (a click, say), draw nothing. Nothing is drawn while the mouse moves. The window after the events below is drawn under them.