io.tlf.jme.jfx.JavaFxUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme-jfx Show documentation
Show all versions of jme-jfx Show documentation
jMonkeyEngine library for JavaFX 12+ integration.
The newest version!
package io.tlf.jme.jfx;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import io.tlf.jme.jfx.impl.JmeUpdateLoop;
import io.tlf.jme.jfx.impl.SceneNotifier;
import io.tlf.jme.jfx.injme.JmeFxContainer;
import io.tlf.jme.jfx.injme.JmeFxContainerImpl;
import io.tlf.jme.jfx.util.JfxPlatform;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public class JavaFxUI {
private static final Logger LOGGER = Logger.getLogger(JavaFxUI.class.getName());
private static JavaFxUI INSTANCE;
private static boolean installAdapter = true;
private final Application app;
private JmeFxContainerImpl container;
// the general overlay
private final Group group;
private Scene scene = null;
private final AnchorPane uiscene;
// dialog - overlays an anchorpane to stop clicking background items and allows "darkening" too.
private final AnchorPane dialogAnchorPanel;
private javafx.scene.Node dialog;
private ChangeListener dialogBoundsListener;
private final List updatingItems = new ArrayList<>();
private int camWidth, camHeight;
private JavaFxUI(Application application, boolean installAdapter, String... cssStyles) {
app = application;
Node guiNode = ((SimpleApplication) application).getGuiNode();
if (installAdapter) {
container = (JmeFxContainerImpl) JmeFxContainer.install(application, guiNode);
}
group = new Group();
uiscene = new AnchorPane();
uiscene.setMinWidth(app.getCamera().getWidth());
uiscene.setMinHeight(app.getCamera().getHeight());
group.getChildren().add(uiscene);
if (!installAdapter) {
uiscene.setPickOnBounds(false);
group.setPickOnBounds(false);
}
if (installAdapter) {
scene = new Scene(group, app.getCamera().getWidth(), app.getCamera().getHeight());
scene.setFill(Color.TRANSPARENT);
if (cssStyles != null) {
scene.getStylesheets().addAll(cssStyles);
}
container.setScene(scene, group);
}
dialogAnchorPanel = new AnchorPane();
dialogAnchorPanel.setMinWidth(app.getCamera().getWidth());
dialogAnchorPanel.setMinHeight(app.getCamera().getHeight());
// we get the screen bounds now - as soon as possible - because the bound check is done in an AppState.
// By the time the AppState is initialized the screen size could have changed and our checks would fail.
camWidth = application.getCamera().getWidth();
camHeight = application.getCamera().getHeight();
if (installAdapter) {
application.getStateManager().attach(new JavaFxUpdater());
//Handling now cross input
//Adding input handler
JmeMemoryInputHandler memoryInputHandler = new JmeMemoryInputHandler();
app.getInputManager().addRawInputListener(memoryInputHandler);
//Set allowed to consume function
io.tlf.jme.jfx.JmeFxEventConsumeAllowedFunction allowedFunction = new io.tlf.jme.jfx.JmeFxEventConsumeAllowedFunction(memoryInputHandler);
container.getInputListener().setAllowedToConsumeInputEventFunction(allowedFunction);
}
}
/**
* Initializes the JavaFxUI class ready for use.
* This initialization must be called first before this class is ready for use.
*
* @param application the Jmonkey Application.
* @param cssStyles The global css stylesheets.
*/
public static void initialize(Application application, String... cssStyles) {
INSTANCE = new JavaFxUI(application, installAdapter, cssStyles);
}
public static void setInstallAdapter(boolean installAdapter) {
JavaFxUI.installAdapter = installAdapter;
}
public static JavaFxUI getInstance() {
return INSTANCE;
}
public Group getGroup() {
return group;
}
public Scene getScene() {
return scene;
}
/**
* Set the input focus to JavaFx.
*/
public void grabFocus() {
container.grabFocus();
}
/**
* Set the input focus to JME.
*/
public void loseFocus() {
container.loseFocus();
}
/**
* Attach a javafx.scene.Node to the GUI scene.
*
* @param node the node to attach to the scene.
*/
public void attachChild(javafx.scene.Node node) {
JfxPlatform.runInFxThread(() -> {
uiscene.getChildren().add(node);
recursivelyNotifyChildrenAdded(node);
});
}
/**
* Detach a node from the GUI scene.
*
* @param node the node to detach from the scene.
*/
public void detachChild(javafx.scene.Node node) {
JfxPlatform.runInFxThread(() -> {
uiscene.getChildren().remove(node);
recursivelyNotifyChildrenRemoved(node);
});
}
/**
* Detach a node from the GUI scene.
*
* @param fxId the fx:id of the node.
*/
public void detachChild(String fxId) {
JfxPlatform.runInFxThread(() -> {
javafx.scene.Node node = uiscene.lookup("#" + fxId);
if (node != null) {
uiscene.getChildren().remove(node);
recursivelyNotifyChildrenRemoved(node);
}
});
}
/**
* Get a control from the scene with the given fx:id
*
* @param fxId the String fx:id if the node.
* @return the node with the given name, or null if the node was not found.
*/
public javafx.scene.Node getChild(String fxId) {
return uiscene.lookup("#" + fxId);
}
/**
* Removes all children from the GUI scene.
*/
public void removeAllChildren() {
JfxPlatform.runInFxThread(() -> {
// remove the children before we notify them.
List children = new ArrayList<>(uiscene.getChildren());
uiscene.getChildren().clear();
children.forEach(this::recursivelyNotifyChildrenRemoved);
});
}
private void recursivelyNotifyChildrenRemoved(javafx.scene.Node node) {
// we can do these things in a single execution, rather than individual calls.
boolean sceneNotifier = node instanceof SceneNotifier;
boolean jmeUpdateLoop = node instanceof JmeUpdateLoop;
if (sceneNotifier || jmeUpdateLoop) {
app.enqueue(() -> {
if (sceneNotifier) {
((SceneNotifier) node).onDetached();
}
if (jmeUpdateLoop) {
updatingItems.remove(node);
}
});
}
if (node instanceof Parent) {
Parent parent = (Parent) node;
parent.getChildrenUnmodifiable().forEach(this::recursivelyNotifyChildrenRemoved);
}
}
private void recursivelyNotifyChildrenAdded(javafx.scene.Node node) {
// we can do these things in a single execution, rather than individual calls.
boolean sceneNotifier = node instanceof SceneNotifier;
boolean jmeUpdateLoop = node instanceof JmeUpdateLoop;
if (sceneNotifier || jmeUpdateLoop) {
app.enqueue(() -> {
if (sceneNotifier) {
((SceneNotifier) node).onAttached(app);
}
if (jmeUpdateLoop) {
updatingItems.add(((JmeUpdateLoop) node));
}
});
}
if (node instanceof Parent) {
Parent parent = (Parent) node;
parent.getChildrenUnmodifiable().forEach(this::recursivelyNotifyChildrenAdded);
}
}
/**
* Display a javafx.scene.Node as a centered dialog.
* A dimmed background will be drawn behind the node and any click events will be ignored
* on GUI items behind it.
*
* @param node the node to display as a dialog.
*/
public void showDialog(javafx.scene.Node node) {
showDialog(node, true);
}
/**
* Display a javafx.scene.Node as a centered dialog.
* A dimmed or transparent background will be drawn behind the node and any click events will be ignored
* on GUI items behind it.
*
* @param node the node to display as a dialog.
* @param dimmed whether or not to dim the scene behind the given node.
*/
public void showDialog(javafx.scene.Node node, boolean dimmed) {
// center the dialog
int scrWidth = app.getCamera().getWidth();
int scrHeight = app.getCamera().getHeight();
dialog = node;
dialogBoundsListener = (prop, oldBounds, newBounds) -> {
node.setLayoutX(scrWidth * 0.5 - newBounds.getWidth() * 0.5);
node.setLayoutY(scrHeight * 0.5 - newBounds.getHeight() * 0.5);
};
if (dialog instanceof JmeUpdateLoop) {
updatingItems.add((JmeUpdateLoop) dialog);
}
JfxPlatform.runInFxThread(() -> {
dialogAnchorPanel.setStyle(null);
dialogAnchorPanel.getChildren().add(node);
if (dimmed) {
dialogAnchorPanel.setStyle("-fx-background-color:#000000AA");
}
uiscene.getChildren().add(dialogAnchorPanel);
node.boundsInParentProperty().addListener(dialogBoundsListener);
});
}
/**
* Removes the shown dialog from the scene.
*/
public void removeDialog() {
JfxPlatform.runInFxThread(() -> {
dialogAnchorPanel.getChildren().clear();
uiscene.getChildren().remove(dialogAnchorPanel);
});
if (dialog instanceof JmeUpdateLoop) {
updatingItems.remove(dialog);
}
dialog.boundsInParentProperty().removeListener(dialogBoundsListener);
dialog = null;
dialogBoundsListener = null;
}
/**
* Execute a task on the JavaFX thread.
*
* @param task the task to execute.
*/
public void runInJavaFxThread(Runnable task) {
Platform.runLater(task);
}
/**
* Execute a task on the Jmonkey GL thread.
*
* @param task the task to execute.
*/
public void runInJmeThread(Runnable task) {
app.enqueue(task);
}
/**
* Get the JmeFxContainer that is being used to manage Jfx with Jme.
*
* @return the current implementation of JmeFxContainer in use.
*/
public JmeFxContainer getJmeFxContainer() {
return container;
}
private class JavaFxUpdater extends BaseAppState {
private Camera cam;
@Override
protected void initialize(Application app) {
cam = app.getCamera();
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
@Override
public void update(float tpf) {
if (camWidth != cam.getWidth() || camHeight != cam.getHeight()) {
camWidth = cam.getWidth();
camHeight = cam.getHeight();
}
if (container.isNeedWriteToJme()) {
container.writeToJme();
}
updatingItems.forEach(item -> item.update(tpf));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy