Hi,
in the last couple of weeks I played around with javafx and I really start liking it (I did swing). It would be really neat to find a way of integrate both, Jme and fx for all kinds of non-full-screen desktop applications. However, from what I’ve read, we probably have to hope for an opengl node to ship with Java 8 (see this stackoverflow thread).
Another interesting project I found is lwjgl-fx which basically uses a render-to-image approach as Arthur suggested. It seems, little adaption is needed to use it for Jme.
In the meantime I came up with a quite hackish solution that works for the moment and lets me go on with my project. I use a swing frame (borderless, always on top) and a javafx pane. The dimensions of the swing frame are synched to the dimension of the fx pane via reposition event.
A minimal example would look like this (please excuse my Java, I’m rather a Scala native):
FxWindow.java (contains main method, jme and fx windows are created, reposition events are published on the Eventbus, if size or position of the pane changes)
[java]
package integrationhack;
import java.awt.Canvas;
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import jme3test.post.TestRenderToTexture;
import org.bushe.swing.event.EventBus;
import org.bushe.swing.event.EventServiceExistsException;
import org.bushe.swing.event.EventServiceLocator;
import org.bushe.swing.event.ThreadSafeEventService;
import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;
public class FxWindow extends Application {
private Stage stage;
private Pane jmePane;
private Scene scene;
private class RepositionListener implements ChangeListener<Object> {
@Override
public void changed(ObservableValue<? extends Object> arg0, Object arg1, Object arg2) {
int x = (int) (stage.getX() + scene.getX() + jmePane.getLocalToSceneTransform().getTx());
int y = (int) (stage.getY() + scene.getY() + jmePane.getLocalToSceneTransform().getTy());
int w = (int) jmePane.getWidth();
int h = (int) jmePane.getHeight();
EventBus.publish(new JmeRepositionEvent(x, y, w, h));
}
}
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
VBox root = new VBox();
MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("File");
Menu menuEdit = new Menu("Edit");
menuEdit.getItems().add(new MenuItem("Buharrh"));
menuBar.getMenus().addAll(menuFile, menuEdit);
root.getChildren().add(menuBar);
jmePane = new Pane();
ChangeListener<Object> repoListener = new RepositionListener();
VBox.setVgrow(jmePane, Priority.ALWAYS);
root.getChildren().add(jmePane);
stage.setTitle("JavaFx with JmeFrame");
scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.exit(0);
}
});
stage.xProperty().addListener(repoListener);
stage.yProperty().addListener(repoListener);
stage.getScene().xProperty().addListener(repoListener);
stage.getScene().yProperty().addListener(repoListener);
jmePane.widthProperty().addListener(repoListener);
jmePane.heightProperty().addListener(repoListener);
stage.show();
}
public static void main(String[] args) {
try {
EventServiceLocator.setEventService(EventServiceLocator.SERVICE_NAME_SWING_EVENT_SERVICE,
new ThreadSafeEventService());
} catch (EventServiceExistsException e) {
e.printStackTrace();
}
final com.jme3.app.Application rtt = new TestRenderToTexture();
AppSettings settings = new AppSettings(true);
settings.setVSync(true);
rtt.setSettings(settings);
rtt.createCanvas();
JmeCanvasContext ctx = (JmeCanvasContext) rtt.getContext();
Canvas canvas = ctx.getCanvas();
ctx.setSystemListener(rtt);
new JmeWindow(canvas);
rtt.startCanvas();
rtt.enqueue(new Callable<Void>() {
public Void call() {
rtt.setPauseOnLostFocus(false);
((SimpleApplication) rtt).getFlyByCamera().setDragToRotate(true);
return null;
}
});
launch(args);
}
}
[/java]
JmeWindow.java (just a borderless frame that listens on reposition events)
[java]
package integrationhack;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.bushe.swing.event.EventBus;
import org.bushe.swing.event.EventSubscriber;
public class JmeWindow implements EventSubscriber<JmeRepositionEvent> {
private JFrame frame;
private Canvas canvas;
public JmeWindow(Canvas canvas) {
this.canvas = canvas;
EventBus.subscribeStrongly(JmeRepositionEvent.class, this);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
initialize();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private void initialize() {
JPanel panel = new JPanel(new BorderLayout(0, 0));
panel.add(canvas);
frame = new JFrame();
frame.setSize(new Dimension(300, 200));
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setAlwaysOnTop(true);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setResizable(false);
frame.setUndecorated(true);
frame.setVisible(true);
}
@Override
public void onEvent(JmeRepositionEvent event) {
frame.setLocation(event.x, event.y);
frame.setSize(event.w, event.h);
}
}
[/java]
JmeRepositionEvent.java (used for notifiing the JmeWindow on position updates via Eventbus)
[java]
package integrationhack;
public class JmeRepositionEvent {
int x, y, w, h;
public JmeRepositionEvent(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
[/java]
I know this is only a temporary solution, but it bridges the waiting time for me. I really hope someone comes up with a robust integration in the near future.