JavaFX embedded in jme3

Ah thats it, thank you very much :slight_smile: The scenebuilder does not use this by default.

With a few adjustments to get both sceneviewer and the Embedding to work fine with it.

(btw yes there is some color profile error in the embedded browser)

TestCase
[java]
package de.visiongamestudios.gui;

import java.io.IOException;
import java.net.URL;

import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;

import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;

public class Test extends SimpleApplication {

public static void main(final String[] args) {
	new Test().start();
}

@Override
public void simpleInitApp() {
	this.setPauseOnLostFocus(false);
	this.flyCam.setDragToRotate(true);
	final JmeFxContainer jmefx = new JmeFxContainer(this.getAssetManager(), this, false);
	Platform.runLater(new Runnable() {
		@Override
		public void run() {
			final Group root;
			try {
				final FXMLLoader fxmlLoader = new FXMLLoader();
				final URL location = this.getClass().getResource("test.fxml");
				fxmlLoader.setLocation(location);
				fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
				root = (Group) fxmlLoader.load(location.openStream());

				final Testcontroller controller = fxmlLoader.getController();
				controller.loadWebPage("http://jmonkeyengine.org/showcase/");

				controller.setRootDimension(Test.this.settings.getWidth(), Test.this.settings.getHeight());

				final Scene scene = new Scene(root);
				jmefx.setScene(scene);
				scene.setFill(new Color(0, 0, 0, 0));
			} catch (final IOException e) {
				e.printStackTrace();
			}
		}
	});

	this.guiNode.attachChild(jmefx.getJmeNode());
	this.inputManager.addRawInputListener(jmefx);

	this.viewPort.setBackgroundColor(ColorRGBA.Red);
}

}
[/java]

The fxml file that describes the gui and defines the used controller

<?xml version="1.0" encoding="UTF-8"?>

<?import de.visiongamestudios.gui.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.web.*?>

<Group id="AnchorPane" autoSizeChildren="true" layoutX="0.0" layoutY="0.0" scaleX="1.0" scaleY="1.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="de.visiongamestudios.gui.Testcontroller">
  <children>
    <StackPane fx:id="pseudoRoot" layoutX="0.0" layoutY="0.0" prefHeight="800.0" prefWidth="1024.0">
      <children>
        <BorderPane prefHeight="0.0" prefWidth="0.0">
          <center>
            <WebView fx:id="browser" prefHeight="0.0" prefWidth="0.0" />
          </center>
          <top>
            <StackPane prefHeight="-1.0" prefWidth="200.0">
              <children>
                <Button alignment="TOP_LEFT" mnemonicParsing="false" text="Button tl" StackPane.alignment="TOP_RIGHT" />
                <Button alignment="TOP_RIGHT" mnemonicParsing="false" text="Button br" StackPane.alignment="TOP_LEFT" />
              </children>
            </StackPane>
          </top>
        </BorderPane>
      </children>
    </StackPane>
  </children>
</Group>

Funny layouting bug

Today raw as the code tag explodes the forums layout due to some kind of bug

The controller, handles viewport != designer size by setting the uppermost prefered sizes to the actual ones. For non hacky layouts this should always work.
Without it, it did not work, as I was unable to find something like similar always use maximum width.
[java]
package de.visiongamestudios.gui;

import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebView;

public class Testcontroller {
@FXML
private WebView browser;
@FXML
private StackPane pseudoRoot;

@FXML
private void initialize() {
}

public void setRootDimension(final int x, final int y) {
	this.pseudoRoot.setPrefHeight(y);
	this.pseudoRoot.setPrefWidth(x);
}

public void loadWebPage(final String string) {
	this.browser.getEngine().load(string);
}

}
[/java]

Regarding the controller for resizing, scene should already follow resizing screen properly. Then, if you want to make your components/layout depended on that, you can use code like
[java]
console.prefWidthProperty().bind(scene.widthProperty())
menuBar.prefWidthProperty().bind(scene.widthProperty());
[/java]
etc.

Hm yeah thats a lot cleaner.

Unfortunalty I have found one issue,
When I have a scene in jfx (empty it works) and resize the window, the whole application freezes.

Do you have any Idea how to solve this?

[java]
package de.visiongamestudios.gui;

import java.io.IOException;
import java.net.URL;

import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;

import org.lwjgl.opengl.Display;

import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;

public class Test extends SimpleApplication {

public static void main(final String[] args) {
	new Test().start();
}

private JmeFxContainer	jmefx;

@Override
public void simpleInitApp() {
	this.setPauseOnLostFocus(false);
	this.flyCam.setDragToRotate(true);
	this.jmefx = new JmeFxContainer(this.getAssetManager(), this, false);
	Platform.runLater(new Runnable() {
		@Override
		public void run() {
			final Group root;
			try {
				final FXMLLoader fxmlLoader = new FXMLLoader();
				final URL location = this.getClass().getResource("test.fxml");
				fxmlLoader.setLocation(location);
				fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
				root = (Group) fxmlLoader.load(location.openStream());

				final Testcontroller controller = fxmlLoader.getController();
				controller.loadWebPage("http://jmonkeyengine.org/showcase/");
				controller.setRootDimension(Test.this.settings.getWidth(), Test.this.settings.getHeight());

				final Scene scene = new Scene(root);
				Test.this.jmefx.setScene(scene);
				scene.setFill(new Color(0, 0, 0, 0));
			} catch (final IOException e) {
				e.printStackTrace();
			}
		}
	});

	this.guiNode.attachChild(this.jmefx.getJmeNode());
	this.inputManager.addRawInputListener(this.jmefx);

	this.viewPort.setBackgroundColor(ColorRGBA.Red);

	Display.setResizable(true);
}

@Override
public void simpleUpdate(final float tpf) {
	if (Display.wasResized()) {
		// keep settings in sync with the actual Display
		int w = Display.getWidth();
		int h = Display.getHeight();
		if (w < 2) {
			w = 2;
		}
		if (h < 2) {
			h = 2;
		}
		this.settings.setWidth(Display.getWidth());
		this.settings.setHeight(Display.getHeight());
		this.reshape(this.settings.getWidth(), this.settings.getHeight());
	}
}

}

[/java]

I have used your test class (obviously with different scene, but rest the same) and nothing bad happens - it resizes properly. Are you able to get a stacktrace from ‘frozen’ state, or is it completely broken?

Complelte frozen, the renderer does nto seem to update at all. Not sure if this is related to me using linux.

Can you send me your scene to verify if it works with that one?

This is the scene I have pasted few mails above.
Please try to get a stack trace from the frozen state. jstack should work well under linux.

Here are the Stacktraces of blocked threads:

Name: LWJGL Renderer Thread
State: WAITING on java.util.concurrent.Semaphore$NonfairSync@326a251b
Total blocked: 8  Total waited: 21

Stack trace: 
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
de.visiongamestudios.gui.JmeFxContainer.handleResize(JmeFxContainer.java:216)
de.visiongamestudios.gui.JmeFxContainer.access$0(JmeFxContainer.java:213)
de.visiongamestudios.gui.JmeFxContainer$2.updateLogicalState(JmeFxContainer.java:175)
com.jme3.scene.Node.updateLogicalState(Node.java:152)
com.jme3.app.SimpleApplication.update(SimpleApplication.java:245)
com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
java.lang.Thread.run(Thread.java:744)
Name: QuantumRenderer-0
State: WAITING on java.util.concurrent.Semaphore$NonfairSync@326a251b
Total blocked: 10  Total waited: 9

Stack trace: 
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
de.visiongamestudios.gui.JmeFxContainer.paintComponent(JmeFxContainer.java:324)
de.visiongamestudios.gui.JmeFxContainer$HostContainer.repaint(JmeFxContainer.java:435)
com.sun.javafx.tk.quantum.EmbeddedScene.uploadPixels(EmbeddedScene.java:142)
com.sun.javafx.tk.quantum.EmbeddedState.uploadPixels(EmbeddedState.java:54)
com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:168)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:129)
java.lang.Thread.run(Thread.java:744)
Name: Thread-5
State: WAITING on java.lang.StringBuilder@5a7adbc2
Total blocked: 152  Total waited: 179

Stack trace: 
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
com.sun.glass.ui.InvokeLaterDispatcher.run(InvokeLaterDispatcher.java:126)
Name: JavaFX Application Thread
State: WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@71d7b65d owned by: QuantumRenderer-0
Total blocked: 18  Total waited: 8

Stack trace: 
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
com.sun.javafx.tk.quantum.EmbeddedScene.getPixels(EmbeddedScene.java:191)
de.visiongamestudios.gui.JmeFxContainer.paintComponent(JmeFxContainer.java:332)
de.visiongamestudios.gui.JmeFxContainer$HostContainer.repaint(JmeFxContainer.java:435)
de.visiongamestudios.gui.JmeFxContainer$3.run(JmeFxContainer.java:237)
com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:301)
com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:298)
java.security.AccessController.doPrivileged(Native Method)
com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:298)
com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:48)
com.sun.glass.ui.gtk.GtkApplication$6$1.run(GtkApplication.java:149)
java.lang.Thread.run(Thread.java:744)

Looks to me like the quantum renderer blocks and the lwjgl thread manage to block each other somehow.

And for the linenumbers the current version of the jfx container I use, with the added cleanup appstate.
[java]
package de.visiongamestudios.gui;

import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Camera;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.input.TransferMode;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import javafx.stage.WindowEvent;

import javax.swing.JFrame;

import org.lwjgl.opengl.Display;

import com.jme3.app.Application;
import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.awt.AwtKeyInput;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils;
import com.sun.javafx.application.PlatformImpl;
import com.sun.javafx.cursor.CursorFrame;
import com.sun.javafx.embed.AbstractEvents;
import com.sun.javafx.embed.EmbeddedSceneDSInterface;
import com.sun.javafx.embed.EmbeddedSceneInterface;
import com.sun.javafx.embed.EmbeddedStageInterface;
import com.sun.javafx.embed.HostDragStartListener;
import com.sun.javafx.embed.HostInterface;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.scene.SceneHelper.SceneAccessor;
import com.sun.javafx.stage.EmbeddedWindow;
import com.sun.javafx.tk.Toolkit;

/**

  • Need to pass -Dprism.dirtyopts=false on startup
    */

public class JmeFxContainer implements RawInputListener {

EmbeddedStageInterface	stagePeer;
EmbeddedSceneInterface	scenePeer;
volatile EmbeddedWindow	stage;
HostContainer			hostContainer;

int						pWidth;
int						pHeight;

volatile Scene			scene;

Picture					picture;
Image					jmeImage;
Texture2D				tex;
ByteBuffer				jmeData;
ByteBuffer				fxData;
boolean					fxDataReady	= false;

int						oldX		= -1;
int						oldY		= -1;
boolean					focus;
Application				app;
boolean					fullScreenSuppport;

public static JmeFxContainer install(final Application app, final Node guiNode, final boolean fullScreenSupport) {
	final JmeFxContainer ctr = new JmeFxContainer(app.getAssetManager(), app, fullScreenSupport);
	guiNode.attachChild(ctr.getJmeNode());
	app.getInputManager().addRawInputListener(ctr);

	if (fullScreenSupport) {
		ctr.installSceneAccessorHack();
	}

	return ctr;
}

JmeFxContainer(final AssetManager assetManager, final Application app, final boolean fullScreenSupport) {

	app.getStateManager().attach(new AppState() {

		@Override
		public void update(final float tpf) {

		}

		@Override
		public void stateDetached(final AppStateManager stateManager) {

		}

		@Override
		public void stateAttached(final AppStateManager stateManager) {

		}

		@Override
		public void setEnabled(final boolean active) {

		}

		@Override
		public void render(final RenderManager rm) {

		}

		@Override
		public void postRender() {

		}

		@Override
		public boolean isInitialized() {
			return true;
		}

		@Override
		public boolean isEnabled() {
			return false;
		}

		@Override
		public void initialize(final AppStateManager stateManager, final Application app) {
			// TODO Auto-generated method stub

		}

		@Override
		public void cleanup() {
			Platform.exit();
		}
	});

	this.app = app;
	this.fullScreenSuppport = fullScreenSupport;

	this.hostContainer = new HostContainer();
	this.picture = new Picture("JavaFXContainer", true) {
		@Override
		public void updateLogicalState(final float tpf) {
			if (JmeFxContainer.this.stagePeer != null) {

				if (Display.getWidth() != JmeFxContainer.this.pWidth
						|| Display.getHeight() != JmeFxContainer.this.pHeight) {
					JmeFxContainer.this.handleResize();
				}

				final int x = Display.getX() + (Display.isFullscreen() ? 0 : 3);
				final int y = Display.getY() + (Display.isFullscreen() ? 0 : 25);
				if (JmeFxContainer.this.oldX != x || JmeFxContainer.this.oldY != y) {
					JmeFxContainer.this.oldX = x;
					JmeFxContainer.this.oldY = y;
					Platform.runLater(new Runnable() {
						@Override
						public void run() {
							JmeFxContainer.this.stagePeer.setLocation(x, y);
						}
					});
				}

			}
			super.updateLogicalState(tpf);
		}
	};

	this.picture.move(0, 0, -1);
	this.picture.setPosition(0, 0);

	this.handleResize();

	this.tex = new Texture2D(this.jmeImage);
	this.picture.setTexture(assetManager, this.tex, true);

	this.initFx();
}

public void reshape(final int width, final int width2) {
	this.handleResize();

	this.tex = new Texture2D(this.jmeImage);
}

private void handleResize() {

	try {
		this.imageExchange.acquire();

		this.pWidth = Display.getWidth();
		this.pHeight = Display.getHeight();

		this.picture.setWidth(this.pWidth);
		this.picture.setHeight(this.pHeight);
		this.jmeData = BufferUtils.createByteBuffer(this.pWidth * this.pHeight * 4);
		this.fxData = BufferUtils.createByteBuffer(this.pWidth * this.pHeight * 4);

		this.jmeImage = new Image(Format.RGBA8, this.pWidth, this.pHeight, this.jmeData);
		if (this.tex != null) {
			this.tex.setImage(this.jmeImage);
		}

		if (this.stagePeer != null) {
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					JmeFxContainer.this.stagePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
					JmeFxContainer.this.scenePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
					JmeFxContainer.this.hostContainer.repaint();
				}
			});
		}

	} catch (final Exception exc) {
		exc.printStackTrace();
	} finally {
		this.imageExchange.release();
	}
}

public Picture getJmeNode() {
	return this.picture;
}

private void initFx() {
	PlatformImpl.startup(new Runnable() {
		@Override
		public void run() {
			// No need to do anything here
		}
	});

}

void setFxEnabled(final boolean enabled) {

}

public Scene getScene() {
	return this.scene;
}

public void setScene(final Scene newScene) {
	if (Toolkit.getToolkit().isFxUserThread()) {
		this.setSceneImpl(newScene);
	} else {
		final CountDownLatch initLatch = new CountDownLatch(1);
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				JmeFxContainer.this.setSceneImpl(newScene);
				initLatch.countDown();
			}
		});
		try {
			initLatch.await();
		} catch (final InterruptedException z) {
			z.printStackTrace(System.err);
		}
	}
}

/*
 * Called on JavaFX app thread.
 */
private void setSceneImpl(final Scene newScene) {
	if (this.stage != null && newScene == null) {
		this.stage.hide();
		this.stage = null;
	}
	this.scene = newScene;
	if (this.stage == null && newScene != null) {
		this.stage = new EmbeddedWindow(this.hostContainer);
	}
	if (this.stage != null) {
		this.stage.setScene(newScene);
		if (!this.stage.isShowing()) {
			this.stage.show();
		}
	}
}

public Window getStage() {
	return this.stage;
}

private Semaphore	imageExchange	= new Semaphore(1);
protected JFrame	debugframe;

void paintComponent() {
	if (this.scenePeer == null) {
		return;
	}
	try {

		this.imageExchange.acquire();

		final ByteBuffer data = this.fxData;
		data.clear();

		final IntBuffer buf = data.asIntBuffer();

		final long start = System.currentTimeMillis();
		if (!this.scenePeer.getPixels(buf, this.pWidth, this.pHeight)) {
			return;
		}

		if (this.fullScreenSuppport) {
			for (final PopupSnapper ps : this.activeSnappers) {
				ps.paint(buf, this.pWidth, this.pHeight);
			}
		}

		data.flip();
		this.fxDataReady = true;

	} catch (final Exception exc) {
		exc.printStackTrace();
	} finally {
		this.imageExchange.release();
	}
	this.app.enqueue(new Callable<Void>() {
		@Override
		public Void call() throws Exception {
			final boolean updateImage = JmeFxContainer.this.imageExchange.tryAcquire();
			// we update only if we can do that in nonblocking mode
			// if would need to block, it means that another callable with newer data will be
			// enqueued soon, so we can just ignore this repaint
			if (updateImage) {
				try {
					if (JmeFxContainer.this.fxDataReady) {
						JmeFxContainer.this.fxDataReady = false;
						final ByteBuffer tmp = JmeFxContainer.this.jmeData;
						JmeFxContainer.this.jmeData = JmeFxContainer.this.fxData;
						JmeFxContainer.this.fxData = tmp;
					}
				} finally {
					JmeFxContainer.this.imageExchange.release();
				}
				JmeFxContainer.this.jmeImage.setData(JmeFxContainer.this.jmeData);
			} else {
				// System.out.println("Skipping update due to contention");
			}
			return null;
		}
	});

}

private class HostContainer implements HostInterface {

	@Override
	public void setEmbeddedStage(final EmbeddedStageInterface embeddedStage) {
		JmeFxContainer.this.stagePeer = embeddedStage;
		if (JmeFxContainer.this.stagePeer == null) {
			return;
		}
		if (JmeFxContainer.this.pWidth > 0 && JmeFxContainer.this.pHeight > 0) {
			JmeFxContainer.this.stagePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
		}

		JmeFxContainer.this.stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
	}

	@Override
	public void setEmbeddedScene(final EmbeddedSceneInterface embeddedScene) {
		JmeFxContainer.this.scenePeer = embeddedScene;
		if (JmeFxContainer.this.scenePeer == null) {
			return;
		}
		if (JmeFxContainer.this.pWidth > 0 && JmeFxContainer.this.pHeight > 0) {
			JmeFxContainer.this.scenePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
		}

		JmeFxContainer.this.scenePeer.setDragStartListener(new HostDragStartListener() {

			@Override
			public void dragStarted(final EmbeddedSceneDSInterface dragSource, final TransferMode dragAction) {
				System.out.println("Dragging");
			}
		});

	}

	@Override
	public boolean requestFocus() {
		System.out.println("Called requestFocus");
		return true;
	}

	@Override
	public boolean traverseFocusOut(final boolean forward) {
		System.out.println("Called traverseFocusOut " + forward);
		return true;
	}

	@Override
	public void setPreferredSize(final int width, final int height) {

	}

	int	repaintCounter	= 0;

	@Override
	public void repaint() {
		// System.out.println("Repainting from host " + (++repaintCounter) + " " + System.currentTimeMillis()%10000);
		JmeFxContainer.this.paintComponent();
	}

	@Override
	public void setEnabled(final boolean enabled) {
		JmeFxContainer.this.setFxEnabled(enabled);
	}

	@Override
	public void setCursor(final CursorFrame cursorFrame) {
		// System.out.println("Called setCursor " + cursorFrame);
	}

	@Override
	public boolean grabFocus() {
		// System.out.println("Called grabFocus");
		return true;
	}

	@Override
	public void ungrabFocus() {
		// System.out.println("Called ungrabFocus");
	}
}

@Override
public void beginInput() {
}

@Override
public void endInput() {
}

@Override
public void onJoyAxisEvent(final JoyAxisEvent evt) {

}

@Override
public void onJoyButtonEvent(final JoyButtonEvent evt) {

}

@Override
public void onMouseMotionEvent(final MouseMotionEvent evt) {

	if (this.scenePeer == null) {
		return;
	}

	final int x = evt.getX();
	final int y = this.pHeight - evt.getY();

	final boolean covered = this.isCovered(x, y);
	if (!covered) {
		return;
	} else {
		evt.setConsumed();
	}

	// not sure if should be grabbing focus on mouse motion event
	// grabFocus();

	int type = AbstractEvents.MOUSEEVENT_MOVED;
	int button = AbstractEvents.MOUSEEVENT_NONE_BUTTON;

	final int wheelRotation = (int) Math.round(evt.getDeltaWheel() / -120.0);

	if (wheelRotation != 0) {
		type = AbstractEvents.MOUSEEVENT_WHEEL;
		button = AbstractEvents.MOUSEEVENT_NONE_BUTTON;
	} else if (this.mouseButtonState[0]) {
		type = AbstractEvents.MOUSEEVENT_DRAGGED;
		button = AbstractEvents.MOUSEEVENT_PRIMARY_BUTTON;
	} else if (this.mouseButtonState[1]) {
		type = AbstractEvents.MOUSEEVENT_DRAGGED;
		button = AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON;
	} else if (this.mouseButtonState[2]) {
		type = AbstractEvents.MOUSEEVENT_DRAGGED;
		button = AbstractEvents.MOUSEEVENT_MIDDLE_BUTTON;
	}

	this.scenePeer.mouseEvent(type, button, this.mouseButtonState[0], this.mouseButtonState[1],
			this.mouseButtonState[2], x, y, Display.getX() + x, Display.getY() + y,
			this.keyStateSet.get(KeyEvent.VK_SHIFT), this.keyStateSet.get(KeyEvent.VK_CONTROL),
			this.keyStateSet.get(KeyEvent.VK_ALT), this.keyStateSet.get(KeyEvent.VK_META), wheelRotation, false);
}

boolean[]	mouseButtonState	= new boolean[3];

@Override
public void onMouseButtonEvent(final MouseButtonEvent evt) {

	// TODO: Process events in separate thread ?
	if (this.scenePeer == null) {
		return;
	}

	final int x = evt.getX();
	final int y = this.pHeight - evt.getY();

	int button;

	switch (evt.getButtonIndex()) {
	case 0:
		button = AbstractEvents.MOUSEEVENT_PRIMARY_BUTTON;
		break;
	case 1:
		button = AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON;
		break;
	case 2:
		button = AbstractEvents.MOUSEEVENT_MIDDLE_BUTTON;
		break;
	default:
		return;
	}

	this.mouseButtonState[evt.getButtonIndex()] = evt.isPressed();

	// seems that generating mouse release without corresponding mouse pressed is causing problems in Scene.ClickGenerator

	final boolean covered = this.isCovered(x, y);
	if (!covered) {
		this.loseFocus();
		return;
	} else {
		evt.setConsumed();
		this.grabFocus();
	}

	int type;
	if (evt.isPressed()) {
		type = AbstractEvents.MOUSEEVENT_PRESSED;
	} else if (evt.isReleased()) {
		type = AbstractEvents.MOUSEEVENT_RELEASED;
		// and clicked ??
	} else {
		return;
	}

	this.scenePeer.mouseEvent(type, button, this.mouseButtonState[0], this.mouseButtonState[1],
			this.mouseButtonState[2], x, y, Display.getX() + x, Display.getY() + y,
			this.keyStateSet.get(KeyEvent.VK_SHIFT), this.keyStateSet.get(KeyEvent.VK_CONTROL),
			this.keyStateSet.get(KeyEvent.VK_ALT), this.keyStateSet.get(KeyEvent.VK_META), 0,
			button == AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON);

}

private boolean isCovered(final int x, final int y) {
	if (x < 0 || x >= this.pWidth) {
		return false;
	}
	if (y < 0 || y >= this.pHeight) {
		return false;
	}
	final ByteBuffer data = this.jmeImage.getData(0);
	data.limit(data.capacity());
	final int alpha = data.get(3 + 4 * (y * this.pWidth + x));
	data.limit(0);
	return alpha != 0;
}

private void grabFocus() {
	if (!this.focus && this.stagePeer != null) {
		this.stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
		this.focus = true;
	}
}

private void loseFocus() {
	if (this.focus && this.stagePeer != null) {
		this.stagePeer.setFocused(false, AbstractEvents.FOCUSEVENT_DEACTIVATED);
		this.focus = false;
	}
}

private char[]	keyCharSet	= new char[0xFF];
private BitSet	keyStateSet	= new BitSet(0xFF);

int retrieveKeyState() {
	int embedModifiers = 0;

	if (this.keyStateSet.get(KeyEvent.VK_SHIFT)) {
		embedModifiers |= AbstractEvents.MODIFIER_SHIFT;
	}

	if (this.keyStateSet.get(KeyEvent.VK_CONTROL)) {
		embedModifiers |= AbstractEvents.MODIFIER_CONTROL;
	}

	if (this.keyStateSet.get(KeyEvent.VK_ALT)) {
		embedModifiers |= AbstractEvents.MODIFIER_ALT;
	}

	if (this.keyStateSet.get(KeyEvent.VK_META)) {
		embedModifiers |= AbstractEvents.MODIFIER_META;
	}
	return embedModifiers;
}

@Override
public void onKeyEvent(final KeyInputEvent evt) {

	if (this.scenePeer == null) {
		return;
	}

	final char keyChar = evt.getKeyChar();

	int fxKeycode = AwtKeyInput.convertJmeCode(evt.getKeyCode());

	final int keyState = this.retrieveKeyState();
	if (fxKeycode > this.keyCharSet.length) {
		switch (keyChar) {
		case '\\':
			fxKeycode = java.awt.event.KeyEvent.VK_BACK_SLASH;
			break;
		default:
			return;
		}
	}

	if (this.focus) {
		evt.setConsumed();
	}

	if (evt.isRepeating()) {
		final char x = this.keyCharSet[fxKeycode];

		if (this.focus) {
			this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_TYPED, fxKeycode, new char[] { x }, keyState);
		}
	} else if (evt.isPressed()) {
		this.keyCharSet[fxKeycode] = keyChar;
		this.keyStateSet.set(fxKeycode);
		if (this.focus) {
			this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_PRESSED, fxKeycode, new char[] { keyChar }, keyState);
			this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_TYPED, fxKeycode, new char[] { keyChar }, keyState);
		}
	} else {
		final char x = this.keyCharSet[fxKeycode];
		this.keyStateSet.clear(fxKeycode);
		if (this.focus) {
			this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_RELEASED, fxKeycode, new char[] { x }, keyState);
		}
	}

}

@Override
public void onTouchEvent(final TouchEvent evt) {

}

Map<Window, PopupSnapper>	snappers		= new IdentityHashMap<>();
List<PopupSnapper>			activeSnappers	= new CopyOnWriteArrayList<>();

class PopupSnapper {

	private Window	window;
	private Scene	scene;
	WritableImage	img;
	boolean			ignoreRepaint;

	public PopupSnapper(final Window window, final Scene scene) {
		this.window = window;
		this.scene = scene;
	}

	public void paint(final IntBuffer buf, final int pWidth, final int pHeight) {

		try {

			final WritableImage img = this.img;
			if (img == null) {
				return;
			}
			synchronized (this) {
				final PixelReader pr = img.getPixelReader();

				final int w = (int) img.getWidth();
				final int h = (int) img.getHeight();

				final int[] pixels = new int[w * h];
				pr.getPixels(0, 0, w, h, PixelFormat.getIntArgbInstance(), pixels, 0, w);

				final int xoff = (int) (this.window.getX() - JmeFxContainer.this.oldX);
				final int yoff = (int) (this.window.getY() - JmeFxContainer.this.oldY);

				for (int x = 0; x < w; x++) {
					for (int y = 0; y < h; y++) {
						final int offset = x + xoff + (y + yoff) * pWidth;
						final int old = buf.get(offset);
						final int merge = JmeFxContainer.merge(old, pixels[x + y * w]);
						buf.put(offset, merge);
					}
				}
			}
		} catch (final Exception exc) {
			exc.printStackTrace();
		}

	}

	public void repaint() {
		try {
			if (!Color.TRANSPARENT.equals(this.scene.getFill())) {
				this.scene.setFill(Color.TRANSPARENT);
			}

			if (this.img != null) {
				if (this.img.getWidth() != this.scene.getWidth() || this.img.getHeight() != this.scene.getHeight()) {
					this.img = null;
				}
			}
			synchronized (this) {
				this.img = this.scene.snapshot(this.img);
			}
			JmeFxContainer.this.paintComponent();
		} catch (final Exception exc) {
			exc.printStackTrace();
		}
	}

	public void start() {

		try {
			final Field trackerField = Scene.class.getDeclaredField("tracker");
			trackerField.setAccessible(true);
			trackerField.set(this.scene, new PerformanceTracker() {

				@Override
				public void frameRendered() {
					super.frameRendered();
					if (PopupSnapper.this.ignoreRepaint) {
						PopupSnapper.this.ignoreRepaint = false;
						return;
					}
					Platform.runLater(new Runnable() {
						@Override
						public void run() {
							PopupSnapper.this.ignoreRepaint = true;
							PopupSnapper.this.repaint();
						}
					});

				}

				@Override
				public void pulse() {
					super.pulse();
				}

				@Override
				protected long nanoTime() {
					return System.nanoTime();
				}

				@Override
				public void doOutputLog() {

				}

				@Override
				public void doLogEvent(final String s) {

				}
			});
		} catch (final Exception exc) {
			exc.printStackTrace();
		}

		JmeFxContainer.this.activeSnappers.add(this);

	}

	public void stop() {
		JmeFxContainer.this.activeSnappers.remove(this);
	}

}

private void installSceneAccessorHack() {

	try {
		final Field f = SceneHelper.class.getDeclaredField("sceneAccessor");
		f.setAccessible(true);
		final SceneAccessor orig = (SceneAccessor) f.get(null);

		final SceneAccessor sa = new SceneAccessor() {

			@Override
			public void setPaused(final boolean paused) {
				orig.setPaused(paused);
			}

			@Override
			public void parentEffectiveOrientationInvalidated(final Scene scene) {
				orig.parentEffectiveOrientationInvalidated(scene);
			}

			@Override
			public Camera getEffectiveCamera(final Scene scene) {
				return orig.getEffectiveCamera(scene);
			}

			@Override
			public Scene createPopupScene(final Parent root) {
				final Scene scene = orig.createPopupScene(root);
				scene.windowProperty().addListener(new ChangeListener<Window>() {
					@Override
					public void changed(final javafx.beans.value.ObservableValue<? extends Window> observable,
							final Window oldValue, final Window window) {
						window.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
							@Override
							public void handle(final WindowEvent event) {
								if (Display.isFullscreen()) {
									final PopupSnapper ps = new PopupSnapper(window, scene);
									JmeFxContainer.this.snappers.put(window, ps);
									ps.start();
								}
							}
						});
					};
				});

				scene.windowProperty().addListener(new ChangeListener<Window>() {
					@Override
					public void changed(final javafx.beans.value.ObservableValue<? extends Window> observable,
							final Window oldValue, final Window window) {
						window.addEventHandler(WindowEvent.WINDOW_HIDDEN, new EventHandler<WindowEvent>() {
							@Override
							public void handle(final WindowEvent event) {
								if (Display.isFullscreen()) {
									final PopupSnapper ps = JmeFxContainer.this.snappers.remove(window);
									if (ps == null) {
										System.out.println("Cannot find snapper for window " + window);
									} else {
										ps.stop();
									}
								}
							}
						});
					};
				});

				return scene;
			}

		};

		f.set(null, sa);
	} catch (final Exception exc) {
		exc.printStackTrace();
	}
}

static int merge(final int bg, final int src) {

	// TODO: optimize it

	final int sa = src >>> 24;

	if (sa == 0) {
		return bg;
	}

	final int ba = bg >>> 24;

	final int rb = (src & 0x00ff00ff) * sa + (bg & 0x00ff00ff) * (0xff - sa) & 0xff00ff00;
	final int g = (src & 0x0000ff00) * sa + (bg & 0x0000ff00) * (0xff - sa) & 0x00ff0000;
	final int a = sa + (ba * (0xff - sa) >> 8);

	return a << 24 | (rb | g) >>> 8;
}

}
[/java]

Thanks, I’ll take a look in the evening.
Just to let you know - it seems I have managed to mix up the colors (RGBA versus BGRA) - this shows that testing with greyscale UI is not very good idea… I will try to fix it today evening as well.

Ah, that would explain why the jme website logo is a bit color false :slight_smile:

I have another small addition,

when displaying a Scene, then setting it to null (for example screenshot mode),
the actual jme texture stays visible, but is no longer being updated (as the jfx side works as intended)

This small addition syncs the stages visibility with the one of the jme picture. :slight_smile:
[java]
/*
* Called on JavaFX app thread.
*/
private void setSceneImpl(final Scene newScene) {
if (this.stage != null && newScene == null) {
this.stage.hide();
this.stage = null;
}
++ this.app.enqueue(new Callable<Void>() {
++ @Override
++ public Void call() {
++ JmeFxContainer.this.picture.setCullHint(newScene == null ? CullHint.Always : CullHint.Never);
++ return null;
++ }
++ });

	this.scene = newScene;
	if (this.stage == null &amp;&amp; newScene != null) {
		this.stage = new EmbeddedWindow(this.hostContainer);
	}
	if (this.stage != null) {
		this.stage.setScene(newScene);
		if (!this.stage.isShowing()) {
			this.stage.show();
		}
	}
}

[/java]

I have incorporated deadlock fix, two of your small extensions above and proper handling for colors. It requires jme3 patch from http://hub.jmonkeyengine.org/forum/topic/support-for-argb8-and-bgra8-textures/
Unfortunately, in the process, full screen support regressed considerably. I will try to fix it, but at the moment it is hardly usable at all.

Well that is (luckily) of little concern to me, as I only plan to have pseudofullscreen anyway (eg undecorated window at location0,0 with screen resolution).

But yes, in general it is probably a good thing if it works as well.

I played a bit around today how to do internal windows ect, and came to a question?.

Would you like to make this a seperate library for jme3 with additional logic to do most of the default tasks, eg rescaling huds to fullscreen, hiding a bit of the complexity ect, (Or at least not mind me doing this?)

As I have started today to allow a normal approach, eg using fullscreen huds and using internalframes to build a gui application. This results in somethind similar to this;

[java]
final GuiManager testguiManager = new GuiManager(this.guiNode, this.assetManager, this, false);
/**
* 2d gui, use the default input provider
*/
this.inputManager.addRawInputListener(testguiManager.getInputRedirector());

	final TestHud testhud = new TestHud();
	testguiManager.attachHudAsync(testhud);

[/java]

Eg in this case the gui manager takes care of providing a non filled root scene, where everything else can be inserted without further complications.

The user written gui then extends AbstractHud or AbstractWindow depending on the use case.

[java]
public class TestHud extends AbstractHud {

@Override
protected Region innerInit() throws Exception {
	final FXMLLoader fxmlLoader = new FXMLLoader();
	final URL location = this.getClass().getResource("loading_screen.fxml");
	fxmlLoader.setLocation(location);
	fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
	return fxmlLoader.load(location.openStream());
}

}
[/java]

Feel free to do whatever you need - I would just like for the base class to cover only painting/event handling, without any specific logic for handling things inside scene. If you need to expose any extra things, like resize callbacks etc, feel free to add them - I will make the changes in my version as well.

Great, one question remains, is your source already under some license? Else I would like to use the same license jme (New BSD http://opensource.org/licenses/BSD-3-Clause) uses as well, to simplify matters for using this stuff.

For all the purposes, my sources is public domain, but you are ok to assume it is BSD, in case somebody would like to sue me for it being wrong :wink:

http://empirephoenix.github.io/JME3-JFX/

I added you as contributor, so you can pull push :slight_smile:

Also i added a rudimentary mousecursor handeling in the pushed version.

@Empire Phoenix said: http://empirephoenix.github.io/JME3-JFX/

I added you as contributor, so you can pull push :slight_smile:

Also i added a rudimentary mousecursor handeling in the pushed version.

Hi,

I just forked the project to play around with it a bit. Coming from web development I personaly think that javaFX could develop into an important technology for frontends from oracle and as I am also interested in the monkey engine this is a cool project :slight_smile:

Yep, I think jfx is a natural choice for anyone coming from enterprise java development in the coming years. (Also it kicks all the other current gui frameworks for jme feature wise)

@Empire Phoenix said: Yep, I think jfx is a natural choice for anyone coming from enterprise java development in the coming years. (Also it kicks all the other current gui frameworks for jme feature wise)

btw I can’t find a working build script. Which jdk / javaFX versions do you use ?

I would prefer to work with the regular JMonkey Jdk version 1.7 which should already include javaFX 2.1 (I guess). This should prevent incompatibilites to all other users because of using “bleeding edge” technology :wink:

I come from svn and maven so the git and gradle is also a little new for me :slight_smile: but thats exactly whyI’m doing this to learn and play :smiley: