Thomas Klier
Thomas Klier

Reputation: 469

JavaFX: Mouse lags behind drawing

I have a JavaFX app where I draw on a canvas. The drawing follows the mouse. This movement is a bit laggy, because the rendering takes some time. That's ok so far. But when I stop the mouse, its coordinates are sometimes still on an old position.

The following code reproduces the problem:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.stage.Stage;

public class TestApp extends Application
{
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Pane p = new Pane();

        final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
        p.getChildren().add(ellipse);

        p.setOnMouseMoved(event ->
        {
            ellipse.setCenterX(event.getX());
            ellipse.setCenterY(event.getY());
            Platform.runLater(() -> doSomeWork());
        });

        Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    void doSomeWork()
    {
        try
        {
            Thread.sleep(100);
        }
        catch (Exception ignore) { }
    }
}

When you move the mouse fast and stop abrupt, the Circle is sometimes not under the mouse.

I've play with use or don't use Platform.runLater() or the call order. No success.

Edit: I can't reproduce this behaviour under Windows.

Upvotes: 0

Views: 760

Answers (1)

MalaKa
MalaKa

Reputation: 3794

My assumption is that onMouseMoved is called on the UI-Thread as well as the Platform.runLater. This blocks the UI-Thread and therefore, the last onMouseMoved calls are discarded (which lets the circle be at the position of the last call from onMouseMoved). As defined in the Platform.runLater-Doc:

Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.

So try to do your work on an extra-thread and publish it to the UI via runLater after the calculation is done:

public class TestApp extends Application
{

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Pane p = new Pane();

        final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
        p.getChildren().add(ellipse);

        p.setOnMouseMoved(event ->
        {
            ellipse.setCenterX(event.getX());
            ellipse.setCenterY(event.getY());
            doSomeWork();
        });

        Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    void doSomeWork()
    {
        new Thread(){
            public void run(){

                try
                {
                    Thread.sleep(100);
                    Platform.runLater(() -> {
                        // ui updates
                    });
                }
                catch (Exception ignore) { }
            }
        }.start();
    }
}

Upvotes: 1

Related Questions