Java Processing 3 PAplet in JavaFX scene as FXNode
Asked Answered
C

5

6

I am trying to make a program for visual analyzing Fractal sets. I choose Processing 3 as drawing library and JavaFX for the user interface. There are some screenshots of the current state:

My GUI:

JavaFX Scene Builder

there is Launcher code:

import Graphics.Canvas2D;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import processing.core.PGraphics;

import java.io.IOException;

public class Launcher extends Application {
    private static Stage primaryStage;

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

    @Override
    public void start(Stage primaryStage) {
        Parent root = loadFXML("MainUI.fxml");
        Scene scene = new Scene(root, 500, 400);
        primaryStage.setTitle("Fractal Analyzer");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setMaximized(true);

        Launcher.primaryStage = primaryStage;
    }

    @Override
    public void init() {

    }

    @Override
    public void stop() {
        System.exit(0);
    }

    public static Stage getPrimaryStage() {
        return primaryStage;
    }

    public  void setCanvas(Canvas2D canvas){

    }



    private Parent loadFXML(String path) {
        try {
            return FXMLLoader.load(getClass().getResource(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.exit(1);
        return null;
    }
}

Testing fractal PAplet:

Processing PAplet window

There is a code of this PAplet:

package Fractal;

import processing.core.PApplet;

public class SirpenskiTriangle extends PApplet {

    public static void main(String[] args) {
        PApplet.main("Fractal.SirpenskiTriangle");
    }

    public void settings() {
        size(640, 640);
        smooth();
        if (frame != null) {
            frame.setResizable(true);
        }
    }

    public void draw() {
        drawTriangle(new Position(300, 20), new Position(620, 620), new Position(20, 620), 0);
        noLoop();
        scale(10f);
    }

    public void setup(){}

    public void drawTriangle(Position top, Position right, Position left, int depth) {
        if (depth > 10) return;

        line(top.x, top.y, right.x, right.y);
        line(right.x, right.y, left.x, left.y);
        line(left.x, left.y, top.x, top.y);

        drawTriangle(top, top.middleWith(right), top.middleWith(left), depth + 1);
        drawTriangle(top.middleWith(left), left.middleWith(right), left, depth + 1);
        drawTriangle(top.middleWith(right), right, left.middleWith(right), depth + 1);
    }

    class Position {
        final float x;
        final float y;

        Position(float x, float y) {
            this.x = x;
            this.y = y;
        }

        Position middleWith(Position other) {
            return new Position((x + other.x) / 2, (y + other.y) / 2);
        }
    }
}

Is there any way to put processing PAplet into JavaFX scene like canvas or something similar?

I hope it can work like this, but this code is invalid: example

Changeful answered 8/10, 2018 at 20:39 Comment(0)
P
6

I have devised two approaches: in the first, we bypass Processing's JavaFX stage creation and point Processing to draw into a JavaFX stage loaded from an FXML file; in the second, we replace Processing's default JavaFX scene with one loaded from an FXML file during runtime.

1. Launching from an FXML

With the first approach we launch the application like we would a JavaFX app (using Application.launch(Launcher.class);), completely bypassing Processing's JavaFX stage creation code.

You'll have to download a slightly modified core.jar for this approach to work, where I've changed the visibility of a few members of the PSurfaceFX and PGraphicsFX2D classes from Protected to Public. The changes allow us to launch JavaFX from our own ... extends Application class, while maintaining access to the members that Processing needs to set during the launch to function.

Processing 3 crashes in FX2D mode when the JDK in use is above Java 8, so I've also made a working version for 8+, since the FXML files usually need at least Java 9 to work.

This is the FXML file I am working with in this example:

enter image description here

With the modified core.jar added to your project's classpath, override initSurface() in your PApplet class with the following snippet. With this code, we bypass the PApplet's call to initFrame() - this is where processing creates its own JavaFX stage, which we do not want it to do.

@Override
protected PSurface initSurface() {
    g = createPrimaryGraphics();
    PSurface genericSurface = g.createSurface();
    PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;

    fxSurface.sketch = this;

    Launcher.surface = fxSurface;

    new Thread(new Runnable() {
        public void run() {
            Application.launch(Launcher.class);
        }
    }).start();

    while (fxSurface.stage == null) {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
        }
    }

    this.surface = fxSurface;
    return fxSurface;
}

Set the PApplet's renderering mode to FX2D like so:

@Override
public void settings() {
    size(0, 0, FX2D);
}

Put the following, or similar, in your Launcher class. In this example, I have manually found the Node that I want to add the canvas object into. There are better, more programmatic, ways of doing this (such as .lookup() using the fx:id of the desired node -- this can be defined in the FXML file). I have also bound the dimensions of the canvas to those of its parent, so when the divisor separating the Master and View panes is dragged, the Processing canvas resizes accordingly.

public class Launcher extends Application {

    public static PSurfaceFX surface;

    @Override
    public void start(Stage primaryStage) throws Exception {

        Canvas canvas = (Canvas) surface.getNative(); // boilerplate
        GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); // boilerplate
        surface.fx.context = graphicsContext; // boilerplate

        primaryStage.setTitle("FXML/Processing");

        VBox root = FXMLLoader.load(new File("c:/Users/Mike/desktop/test.fxml").toURI().toURL());
        SplitPane pane = (SplitPane) root.getChildren().get(1); // Manually get the item I want to add canvas to
        AnchorPane pane2 = (AnchorPane) pane.getItems().get(0); // Manually get the item I want to add canvas to
        pane2.getChildren().add(canvas); // Manually get the item I want to add canvas to

        canvas.widthProperty().bind(pane2.widthProperty());
        canvas.heightProperty().bind(pane2.heightProperty());

        Scene scene = new Scene(root, 800, 800);
        primaryStage.setScene(scene);
        primaryStage.show();

        surface.stage = primaryStage; // boilerplate
    }
}

This is the result:

enter image description here

Also see this Github project -- a basic project showing how a Processing sketch and a FXML JavaFX stage may be integrated using this first approach, but includes a JavaFX Controller to populate @FXMLannotated fields (providing an easy way to first get, and then reference, JavaFX objects in code).


2. Launching, then loading a FXML

This approach works with vanilla Processing. Here, we launch Processing like normal and then replace the default scene with new scene loaded from an FXML file during runtime. This is a simpler approach (and doesn't require using a modified .jar!) but will make JavaFX/Processing interoperability more difficult because we can't use a JavaFX Controller to get fields via FXML injection.

Example PDE code:

import java.util.Map;
import java.nio.file.Paths;

import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

import processing.javafx.PSurfaceFX;

public void setup() {
  size(800, 800, FX2D);
  strokeWeight(3);
}

protected PSurface initSurface() {
  surface = (PSurfaceFX) super.initSurface();
  final Canvas canvas = (Canvas) surface.getNative();
  final Scene oldScene = canvas.getScene();
  final Stage stage = (Stage) oldScene.getWindow();

  try {
    FXMLLoader loader = new FXMLLoader(Paths.get("C:\\path--to--fxml\\stage.fxml").toUri().toURL()); // abs path to fxml file
    final Parent sceneFromFXML = loader.load();
    final Map<String, Object> namespace = loader.getNamespace();

    final Scene newScene = new Scene(sceneFromFXML, stage.getWidth(), stage.getHeight(), false, 
      SceneAntialiasing.BALANCED);
    final AnchorPane pane = (AnchorPane) namespace.get("anchorPane"); // get element by fx:id

    pane.getChildren().add(canvas); // processing to stackPane
    canvas.widthProperty().bind(pane.widthProperty()); // bind canvas dimensions to pane
    canvas.heightProperty().bind(pane.heightProperty()); // bind canvas dimensions to pane

    Platform.runLater(new Runnable() {
      @Override
        public void run() {
        stage.setScene(newScene);
      }
    }
    );
  } 
  catch (IOException e) {
    e.printStackTrace();
  }
  return surface;
}

public void draw() {
  background(125, 125, 98);
  ellipse(200, 200, 200, 200);
  line(0, 0, width, height);
  line(width, 0, 0, height);
}

Result:

enter image description here

…using this FXML file:

enter image description here

Pushcart answered 12/10, 2018 at 15:10 Comment(11)
Please, can you add your FXML file ?Typeset
Your code breaks at this lines ` Canvas canvas = (Canvas) surface.getNative(); // boilerplate GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); // boilerplate surface.fx.context = graphicsContext; // boilerplate `Typeset
@JanČerný FXML file. How does the code break? The Launcher class' surface object is set in the initsurface() override method PApplet, if it's null.Pushcart
the triangle isnt showing and im getting this WARNING: Loading FXML document with JavaFX API of version 9.0.1 by JavaFX runtime of version 8.0.211 java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main at com.sun.glass.ui.Application.chat processing.core.PApplet.main(PApplet.java:10544) at processingfxnew.SirpenskiTriangle.main(SirpenskiTriangle.java:13) but im using java 8Hydatid
@Hydatid Use Java 9 or above.Pushcart
turns out that was just a warning what was causing the error is java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main at com.sun.glass.ui.Application.checkEventThread(Application.java:443) .... at processing.core.PApplet.main(PApplet.java:10544) at processingfxnew.SirpenskiTriangle.main(SirpenskiTriangle.java:13) that line being PApplet.main("processingfxnew.SirpenskiTriangle"); is not showing cause of this im not sure how to fix itHydatid
@Hydatid I couldn't say why either from here... I have an example on my Github of loading a JavaFX stage from an FXML file in Processing. You could use that project as a template.Pushcart
ok thanks i cant find a github with your name can you provide a link?Hydatid
I downloaded your project files and the window appears here too but the processing piece is still not being rendered and im still getting the same error java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main at com.sun.glass.ui.Application.checkEventThread(Application.java:443) And also ..FXMLLoader..56 mo Caused by: java.lang.NullPointerException at javafx.Controller.greenPen(Controller.java:60) ... 66 moreHydatid
Using java8 and your modified core 1.8.jar and modified your fxml to be javafx8 and processings other jars like gluejen-rtHydatid
@Hydatid I'm not certain it will work with 1.8 - just try Java 10!Pushcart
O
1

I have make a fork of processing 3 ( https://github.com/Piervit/processing3-javafx/tree/main) which

  • is build with maven
  • work with java 17
  • only should be use with fx2d
  • provide a javafx node which can be embedded in a frame.
Ornithischian answered 27/8, 2023 at 8:44 Comment(0)
A
0

To make it work, you have to launch your Processing sketch, not the JavaFX Application.

Simply do

PApplet.main(Launcher.class.getName());

Also thanks so much for your help! I had NO idea how I should use the JavaFX stuff that comes with Processing!

Albion answered 11/11, 2018 at 1:6 Comment(3)
Please describe more your solution, because I do not know how you are meaning it.Typeset
Did you still not solve this? I can send you example code later todayAlbion
That would be very nice :) I am more to mathematics than practical Programing, so something like library issues are fatal for me.Typeset
A
0

Okay, this is my code, that works! I copied everything and changed the names. !!!I have not tested this modified code, so don't copy paste everything!!! The raw principle should definetly work though.

If you still have issues or questions, just comment.

Main

public class Main {

    public static void main(String[] args) {

    // Your code starts here, and runs Processing. 
    // This is also, how you normally start Processing sketches.
    PApplet.main(Sketch.class.getName());
    }
}

Sketch

public class Sketch extends PApplet{

    @Override
    public void settings() {
        size(200, 200, FX2D); // Size doesn't really matter
    }


    @Override
    public void setup() {

    }

    @Override
    public void draw() {

    }


// Processing uses this function to determine, 
// how to display everything, how to open the canvas...
// We override the code, that would normally open a window with the normal Processing stuff,
// to open start new JavaFX application in a new Thread.


// micycle's code
    @Override
    protected PSurface initSurface() {
        g = createPrimaryGraphics();
        PSurface genericSurface = g.createSurface();
        PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;

        fxSurface.sketch = this;

        // Because the JavaFX App is being launched by reflection,
        // we can't pass variables to it via constructor, so
        // we have to access it in static context.

        // Here, we give JavaFX the surface.
        ExampleApp.surface = fxSurface;

        // New thread started, so JavaFX and Processing don't interrupt each other.
        new Thread(new Runnable() {
            public void run() {

                // JavaFX way of launching a new Application
                Application.launch(ExampleApp.class);
            }
        }).start();

    while (fxSurface.stage == null) {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {

        }
    }

        this.surface = fxSurface;
        return fxSurface;
    }
}

ExampleApp

public class ExampleApp extends Application {

    public Canvas canvas; // The Canvas you will be drawing to 
    public static PSurfaceFX surface; // The Processing surface



    // JavaFX started, this method is being run to set everything up.
    @Override
    public void start(Stage primaryStage) {

        // This sets up the canvas, and the drawing region.
        canvas = (Canvas) surface.getNative();
        surface.fx.context = canvas.getGraphicsContext2D();
        surface.stage = primaryStage;


        // I'm just loading my FXML file. You can do all JavaFX stuff via code, if you want
        try {

            // !!My root Container is a BorderPane!!
            BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("application.fxml"));
        } catch(IOException e) {
            e.printStackTrace();
        }

        // Getting the Anchor pane, that is in the center of my BorderPane
        AnchorPane pane = (AnchorPane) root.getCenter();

        // The Anchor pane is being used, so the canvas can fill the parent (Center)
        // Canvases don't have a property to fill it's parent, like most Containers do (Because it isn't a container)
        canvas.widthProperty().bind(pane.widthProperty());
        canvas.heightProperty().bind(pane.heightProperty());

        // Adding the canvas to your App
        root.getChildren().add(canvas);

        // Launching the Stage
        primaryStage.setTitle("Example App");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

}
Albion answered 20/11, 2018 at 21:27 Comment(0)
A
0

Okay, since last time , I changed some elements. The canvas's parent is now just a Pane instead of an AnchorPane.

The FXML won't help you much... It is just a BorderPane with another Pane in it, but alright...

 <center>
  <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
     <children>

        <Pane maxHeight="1.7976931348623157E308" VBox.vgrow="ALWAYS" />

     </children>
  </VBox>

So, what I'm doing is taking the Canvas element, Processing creates and just adding it to the Pane.

Albion answered 17/12, 2018 at 15:49 Comment(2)
please don't post several answers to the same question - instead edit and refine the one that solves the problem completely (or as near as you can get :)Baksheesh
I'm new to Stackoverflow so sorry for that. Will keep that in mind!Albion

© 2022 - 2024 — McMap. All rights reserved.