diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java new file mode 100644 index 000000000..0227aff0b --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import java.awt.Component; + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.system.AWTFrameProcessor; +import com.jme3.system.AWTTaskExecutor; + +/** + * An app state dedicated to the rendering of a JMonkey application within an AWT component. + * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + */ +public class AWTComponentAppState extends AbstractAppState { + + private final AWTTaskExecutor executor = AWTTaskExecutor.getInstance(); + + private Component component = null; + + private AWTFrameProcessor processor = null; + + private AWTFrameProcessor.TransferMode transferMode = AWTFrameProcessor.TransferMode.ON_CHANGES; + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + } + + @Override + public void stateAttached(final AppStateManager stateManager) { + processor = new AWTFrameProcessor(); + processor.setTransferMode(transferMode); + + AWTTaskExecutor.getInstance().addToExecute(new Runnable() { + + @Override + public void run() { + processor.bind(component, stateManager.getApplication(), stateManager.getApplication().getViewPort()); + } + + }); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + } + + @Override + public void update(float tpf) { + executor.execute(); + super.update(tpf); + } + + @Override + public void cleanup() { + super.cleanup(); + } + + /** + * Create a new app state dedicated to the rendering of a JMonkey application within the given AWT component. + * @param component the component that is used as rendering output. + */ + public AWTComponentAppState(Component component) { + this.component = component; + } + + /** + * Get the AWT component that is used as rendering output. + * @return the AWT component that is used as rendering output. + * @see #setComponent(Component) + */ + public Component getComponent() { + return component; + } + + /** + * Set the AWT component that is used as rendering output. + * @param component the AWT component that is used as rendering output. + * @see #getComponent() + */ + public void setComponent(Component component) { + this.component = component; + } + + /** + * Get the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @return the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @see #setTransferMode(com.jme3.system.AWTFrameProcessor.TransferMode) + */ + public AWTFrameProcessor.TransferMode getTransferMode(){ + return transferMode; + } + + /** + * Set the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @param mode the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @see #getTransferMode() + */ + public void setTransferMode(AWTFrameProcessor.TransferMode mode) { + this.transferMode = mode; + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java new file mode 100644 index 000000000..cceab1d6e --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.util.Objects; + +import com.jme3.app.Application; +import com.jme3.input.Input; +import com.jme3.input.RawInputListener; +import com.jme3.system.AWTContext; +import com.jme3.system.AWTTaskExecutor; + +/** + * The implementation of the {@link Input} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTInput implements Input { + + protected static final AWTTaskExecutor EXECUTOR = AWTTaskExecutor.getInstance(); + + /** + * The context. + */ + protected final AWTContext context; + + /** + * The raw listener. + */ + protected RawInputListener listener; + + /** + * The input node. + */ + protected Component component; + + /** + * The {@link Application JMonkey application} that provide the context. + */ + protected Application application; + + /** + * Initializes is it. + */ + protected boolean initialized; + + public AWTInput(final AWTContext context) { + this.context = context; + } + + public void bind(final Component component) { + this.component = component; + Objects.requireNonNull(this.component, "binded Component cannot be null"); + } + + public void unbind() { + this.component = null; + } + + @Override + public void initialize() { + if (isInitialized()) return; + initializeImpl(); + initialized = true; + } + + protected void initializeImpl() { + } + + @Override + public void update() { + if (!context.isRenderable()) return; + updateImpl(); + } + + protected void updateImpl() { + } + + @Override + public void destroy() { + unbind(); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java new file mode 100644 index 000000000..0280d8aed --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.system.AWTContext; + + +/** + * The implementation of the {@link KeyInput} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTKeyInput extends AWTInput implements KeyInput, KeyListener{ + + private static final Map KEY_CODE_TO_JME = new HashMap<>(); + + static { + KEY_CODE_TO_JME.put(KeyEvent.VK_ESCAPE, KEY_ESCAPE); + KEY_CODE_TO_JME.put(KeyEvent.VK_0, KEY_0); + KEY_CODE_TO_JME.put(KeyEvent.VK_1, KEY_1); + KEY_CODE_TO_JME.put(KeyEvent.VK_2, KEY_2); + KEY_CODE_TO_JME.put(KeyEvent.VK_3, KEY_3); + KEY_CODE_TO_JME.put(KeyEvent.VK_4, KEY_4); + KEY_CODE_TO_JME.put(KeyEvent.VK_5, KEY_5); + KEY_CODE_TO_JME.put(KeyEvent.VK_6, KEY_6); + KEY_CODE_TO_JME.put(KeyEvent.VK_7, KEY_7); + KEY_CODE_TO_JME.put(KeyEvent.VK_8, KEY_8); + KEY_CODE_TO_JME.put(KeyEvent.VK_9, KEY_9); + KEY_CODE_TO_JME.put(KeyEvent.VK_MINUS, KEY_MINUS); + KEY_CODE_TO_JME.put(KeyEvent.VK_EQUALS, KEY_EQUALS); + KEY_CODE_TO_JME.put(KeyEvent.VK_BACK_SPACE, KEY_BACK); + KEY_CODE_TO_JME.put(KeyEvent.VK_TAB, KEY_TAB); + KEY_CODE_TO_JME.put(KeyEvent.VK_Q, KEY_Q); + KEY_CODE_TO_JME.put(KeyEvent.VK_W, KEY_W); + KEY_CODE_TO_JME.put(KeyEvent.VK_E, KEY_E); + KEY_CODE_TO_JME.put(KeyEvent.VK_R, KEY_R); + KEY_CODE_TO_JME.put(KeyEvent.VK_T, KEY_T); + KEY_CODE_TO_JME.put(KeyEvent.VK_U, KEY_U); + KEY_CODE_TO_JME.put(KeyEvent.VK_I, KEY_I); + KEY_CODE_TO_JME.put(KeyEvent.VK_O, KEY_O); + KEY_CODE_TO_JME.put(KeyEvent.VK_P, KEY_P); + KEY_CODE_TO_JME.put(KeyEvent.VK_OPEN_BRACKET, KEY_LBRACKET); + KEY_CODE_TO_JME.put(KeyEvent.VK_CLOSE_BRACKET, KEY_RBRACKET); + KEY_CODE_TO_JME.put(KeyEvent.VK_ENTER, KEY_RETURN); + KEY_CODE_TO_JME.put(KeyEvent.VK_CONTROL, KEY_LCONTROL); + KEY_CODE_TO_JME.put(KeyEvent.VK_A, KEY_A); + KEY_CODE_TO_JME.put(KeyEvent.VK_S, KEY_S); + KEY_CODE_TO_JME.put(KeyEvent.VK_D, KEY_D); + KEY_CODE_TO_JME.put(KeyEvent.VK_F, KEY_F); + KEY_CODE_TO_JME.put(KeyEvent.VK_G, KEY_G); + KEY_CODE_TO_JME.put(KeyEvent.VK_H, KEY_H); + KEY_CODE_TO_JME.put(KeyEvent.VK_J, KEY_J); + KEY_CODE_TO_JME.put(KeyEvent.VK_Y, KEY_Y); + KEY_CODE_TO_JME.put(KeyEvent.VK_K, KEY_K); + KEY_CODE_TO_JME.put(KeyEvent.VK_L, KEY_L); + KEY_CODE_TO_JME.put(KeyEvent.VK_SEMICOLON, KEY_SEMICOLON); + KEY_CODE_TO_JME.put(KeyEvent.VK_QUOTE, KEY_APOSTROPHE); + KEY_CODE_TO_JME.put(KeyEvent.VK_DEAD_GRAVE, KEY_GRAVE); + KEY_CODE_TO_JME.put(KeyEvent.VK_SHIFT, KEY_LSHIFT); + KEY_CODE_TO_JME.put(KeyEvent.VK_BACK_SLASH, KEY_BACKSLASH); + KEY_CODE_TO_JME.put(KeyEvent.VK_Z, KEY_Z); + KEY_CODE_TO_JME.put(KeyEvent.VK_X, KEY_X); + KEY_CODE_TO_JME.put(KeyEvent.VK_C, KEY_C); + KEY_CODE_TO_JME.put(KeyEvent.VK_V, KEY_V); + KEY_CODE_TO_JME.put(KeyEvent.VK_B, KEY_B); + KEY_CODE_TO_JME.put(KeyEvent.VK_N, KEY_N); + KEY_CODE_TO_JME.put(KeyEvent.VK_M, KEY_M); + KEY_CODE_TO_JME.put(KeyEvent.VK_COMMA, KEY_COMMA); + KEY_CODE_TO_JME.put(KeyEvent.VK_PERIOD, KEY_PERIOD); + KEY_CODE_TO_JME.put(KeyEvent.VK_SLASH, KEY_SLASH); + KEY_CODE_TO_JME.put(KeyEvent.VK_MULTIPLY, KEY_MULTIPLY); + KEY_CODE_TO_JME.put(KeyEvent.VK_SPACE, KEY_SPACE); + KEY_CODE_TO_JME.put(KeyEvent.VK_CAPS_LOCK, KEY_CAPITAL); + KEY_CODE_TO_JME.put(KeyEvent.VK_F1, KEY_F1); + KEY_CODE_TO_JME.put(KeyEvent.VK_F2, KEY_F2); + KEY_CODE_TO_JME.put(KeyEvent.VK_F3, KEY_F3); + KEY_CODE_TO_JME.put(KeyEvent.VK_F4, KEY_F4); + KEY_CODE_TO_JME.put(KeyEvent.VK_F5, KEY_F5); + KEY_CODE_TO_JME.put(KeyEvent.VK_F6, KEY_F6); + KEY_CODE_TO_JME.put(KeyEvent.VK_F7, KEY_F7); + KEY_CODE_TO_JME.put(KeyEvent.VK_F8, KEY_F8); + KEY_CODE_TO_JME.put(KeyEvent.VK_F9, KEY_F9); + KEY_CODE_TO_JME.put(KeyEvent.VK_F10, KEY_F10); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUM_LOCK, KEY_NUMLOCK); + KEY_CODE_TO_JME.put(KeyEvent.VK_SCROLL_LOCK, KEY_SCROLL); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD7, KEY_NUMPAD7); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD8, KEY_NUMPAD8); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD9, KEY_NUMPAD9); + KEY_CODE_TO_JME.put(KeyEvent.VK_SUBTRACT, KEY_SUBTRACT); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD4, KEY_NUMPAD4); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD5, KEY_NUMPAD5); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD6, KEY_NUMPAD6); + KEY_CODE_TO_JME.put(KeyEvent.VK_ADD, KEY_ADD); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD1, KEY_NUMPAD1); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD2, KEY_NUMPAD2); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD3, KEY_NUMPAD3); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD0, KEY_NUMPAD0); + KEY_CODE_TO_JME.put(KeyEvent.VK_DECIMAL, KEY_DECIMAL); + KEY_CODE_TO_JME.put(KeyEvent.VK_F11, KEY_F11); + KEY_CODE_TO_JME.put(KeyEvent.VK_F12, KEY_F12); + KEY_CODE_TO_JME.put(KeyEvent.VK_F13, KEY_F13); + KEY_CODE_TO_JME.put(KeyEvent.VK_F14, KEY_F14); + KEY_CODE_TO_JME.put(KeyEvent.VK_F15, KEY_F15); + KEY_CODE_TO_JME.put(KeyEvent.VK_KANA, KEY_KANA); + KEY_CODE_TO_JME.put(KeyEvent.VK_CONVERT, KEY_CONVERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_NONCONVERT, KEY_NOCONVERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_CIRCUMFLEX, KEY_CIRCUMFLEX); + KEY_CODE_TO_JME.put(KeyEvent.VK_AT, KEY_AT); + KEY_CODE_TO_JME.put(KeyEvent.VK_COLON, KEY_COLON); + KEY_CODE_TO_JME.put(KeyEvent.VK_UNDERSCORE, KEY_UNDERLINE); + KEY_CODE_TO_JME.put(KeyEvent.VK_STOP, KEY_STOP); + KEY_CODE_TO_JME.put(KeyEvent.VK_DIVIDE, KEY_DIVIDE); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAUSE, KEY_PAUSE); + KEY_CODE_TO_JME.put(KeyEvent.VK_HOME, KEY_HOME); + KEY_CODE_TO_JME.put(KeyEvent.VK_UP, KEY_UP); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAGE_UP, KEY_PRIOR); + KEY_CODE_TO_JME.put(KeyEvent.VK_LEFT, KEY_LEFT); + KEY_CODE_TO_JME.put(KeyEvent.VK_RIGHT, KEY_RIGHT); + KEY_CODE_TO_JME.put(KeyEvent.VK_END, KEY_END); + KEY_CODE_TO_JME.put(KeyEvent.VK_DOWN, KEY_DOWN); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAGE_DOWN, KEY_NEXT); + KEY_CODE_TO_JME.put(KeyEvent.VK_INSERT, KEY_INSERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_DELETE, KEY_DELETE); + KEY_CODE_TO_JME.put(KeyEvent.VK_ALT, KEY_LMENU); + KEY_CODE_TO_JME.put(KeyEvent.VK_META, KEY_RCONTROL); + } + + private final LinkedList keyInputEvents; + + public AWTKeyInput(AWTContext context) { + super(context); + keyInputEvents = new LinkedList(); + } + + @Override + public void bind(Component component) { + super.bind(component); + component.addKeyListener(this); + } + + @Override + public void unbind() { + if (component != null) { + component.removeKeyListener(this); + } + super.unbind(); + } + + private void onKeyEvent(KeyEvent keyEvent, boolean pressed) { + + int code = convertKeyCode(keyEvent.getID()); + char keyChar = keyEvent.getKeyChar(); + + final KeyInputEvent event = new KeyInputEvent(code, keyChar, pressed, false); + event.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + keyInputEvents.add(event); + } + + }); + } + + @Override + protected void updateImpl() { + while (!keyInputEvents.isEmpty()) { + listener.onKeyEvent(keyInputEvents.poll()); + } + } + + private int convertKeyCode(int keyCode) { + final Integer code = KEY_CODE_TO_JME.get(keyCode); + return code == null ? KEY_UNKNOWN : code; + } + + @Override + public void keyTyped(KeyEvent e) { + System.out.println("Key typed "+e.getKeyChar()); + //onKeyEvent(e, false); + } + + @Override + public void keyPressed(KeyEvent e) { + System.out.println("Key pressed "+e.getKeyChar()); + onKeyEvent(e, true); + } + + @Override + public void keyReleased(KeyEvent e) { + System.out.println("Key released "+e.getKeyChar()); + onKeyEvent(e, false); + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java new file mode 100644 index 000000000..72b75a5d3 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.system.AWTContext; + +/** + * The implementation of the {@link MouseInput} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTMouseInput extends AWTInput implements MouseInput, MouseListener, MouseMotionListener, MouseWheelListener { + + private static final Map MOUSE_BUTTON_TO_JME = new HashMap<>(); + + static { + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON1, BUTTON_LEFT); + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON2, BUTTON_RIGHT); + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON3, BUTTON_MIDDLE); + } + + /** + * The scale factor for scrolling. + */ + private static final int WHEEL_SCALE = 10; + + private final LinkedList mouseMotionEvents; + + private final LinkedList mouseButtonEvents; + + private int mouseX; + private int mouseY; + private int mouseWheel; + + public AWTMouseInput(AWTContext context) { + super(context); + mouseMotionEvents = new LinkedList(); + mouseButtonEvents = new LinkedList(); + } + + @Override + public void bind(Component component) { + super.bind(component); + component.addMouseListener(this); + component.addMouseMotionListener(this); + component.addMouseWheelListener(this); + } + + @Override + public void unbind() { + if (component != null) { + component.removeMouseListener(this); + component.removeMouseMotionListener(this); + component.removeMouseWheelListener(this); + } + super.unbind(); + } + + @Override + protected void updateImpl() { + while (!mouseMotionEvents.isEmpty()) { + listener.onMouseMotionEvent(mouseMotionEvents.poll()); + } + while (!mouseButtonEvents.isEmpty()) { + listener.onMouseButtonEvent(mouseButtonEvents.poll()); + } + } + + private void onWheelScroll(final double xOffset, final double yOffset) { + + mouseWheel += yOffset; + + final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset)); + mouseMotionEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseMotionEvents.add(mouseMotionEvent); + } + + }); + } + + private void onCursorPos(double xpos, double ypos) { + + int xDelta; + int yDelta; + int x = (int) Math.round(xpos); + int y = context.getHeight() - (int) Math.round(ypos); + + if (mouseX == 0) mouseX = x; + if (mouseY == 0) mouseY = y; + + xDelta = x - mouseX; + yDelta = y - mouseY; + + mouseX = x; + mouseY = y; + + if (xDelta == 0 && yDelta == 0) return; + + final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y, xDelta, yDelta, mouseWheel, 0); + mouseMotionEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseMotionEvents.add(mouseMotionEvent); + } + + }); + } + + private void onMouseButton(MouseEvent event, final boolean pressed) { + + final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(convertButton(event.getButton()), pressed, mouseX, mouseY); + mouseButtonEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseButtonEvents.add(mouseButtonEvent); + } + + }); + } + + private int convertButton(int i) { + final Integer result = MOUSE_BUTTON_TO_JME.get(i); + return result == null ? 0 : result; + } + + @Override + public void setCursorVisible(final boolean visible) { + } + + @Override + public int getButtonCount() { + return 3; + } + + @Override + public void setNativeCursor(JmeCursor cursor) { + } + + @Override + public void mouseDragged(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseMoved(java.awt.event.MouseEvent e) { + onCursorPos(e.getX(), e.getY()); + } + + @Override + public void mouseClicked(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mousePressed(java.awt.event.MouseEvent e) { + onMouseButton(e, true); + } + + @Override + public void mouseReleased(java.awt.event.MouseEvent e) { + onMouseButton(e, false); + } + + @Override + public void mouseEntered(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseExited(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + onWheelScroll(e.getWheelRotation() * WHEEL_SCALE, e.getWheelRotation() * WHEEL_SCALE); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java new file mode 100644 index 000000000..99e745675 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.system.AWTFrameProcessor.TransferMode; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.util.BufferUtils; + + +/** + *

+ * This class enables to update graphics of an AWT component with the result of JMonkey 3D rendering. + *

+ *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTComponentRenderer { + + /** + * The constant RUNNING_STATE. + */ + protected static final int RUNNING_STATE = 1; + /** + * The constant WAITING_STATE. + */ + protected static final int WAITING_STATE = 2; + /** + * The constant DISPOSING_STATE. + */ + protected static final int DISPOSING_STATE = 3; + /** + * The constant DISPOSED_STATE. + */ + protected static final int DISPOSED_STATE = 4; + + /** + * The Frame state. + */ + protected final AtomicInteger frameState; + + /** + * The Image state. + */ + protected final AtomicInteger imageState; + + /** + * The Frame buffer. + */ + protected final FrameBuffer frameBuffer; + + /** + * The Pixel writer. + */ + protected final Graphics pixelWriter; + + /** + * The Frame byte buffer. + */ + protected final ByteBuffer frameByteBuffer; + + + /** + * The transfer mode. + */ + protected final TransferMode transferMode; + + /** + * The byte buffer. + */ + protected final byte[] byteBuffer; + + /** + * The image byte buffer. + */ + protected final int[] imageByteBuffer; + + /** + * The prev image byte buffer. + */ + protected final byte[] prevImageByteBuffer; + + /** + * How many frames need to write else. + */ + protected int frameCount; + + /** + * The width. + */ + private final int width; + + /** + * The height. + */ + private final int height; + + private ColorModel colorModel = null; + + private Component component = null; + + private Graphics2D offGraphics = null; + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param width the width of the component in pixels. + * @param height the height of the component in pixels. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update + * or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + */ + public AWTComponentRenderer(Component destination, final int width, final int height, TransferMode transferMode) { + this(destination, transferMode, null, width, height); + } + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + * @param frameBuffer the JMonkey frame buffer to use (if null is passed, a new default frame buffer is created) + * @param width the width of the component in pixels. + * @param height the height of the component in pixels. + */ + public AWTComponentRenderer(Component destination, TransferMode transferMode, FrameBuffer frameBuffer, int width, int height) { + this.transferMode = transferMode; + this.frameState = new AtomicInteger(WAITING_STATE); + this.imageState = new AtomicInteger(WAITING_STATE); + this.width = frameBuffer != null ? frameBuffer.getWidth() : width; + this.height = frameBuffer != null ? frameBuffer.getHeight() : height; + this.frameCount = 0; + + if (frameBuffer != null) { + this.frameBuffer = frameBuffer; + } else { + this.frameBuffer = new FrameBuffer(width, height, 1); + this.frameBuffer.setDepthBuffer(Image.Format.Depth); + this.frameBuffer.setColorBuffer(Image.Format.RGBA8); + this.frameBuffer.setSrgb(true); + } + + colorModel = ColorModel.getRGBdefault(); + + frameByteBuffer = BufferUtils.createByteBuffer(getWidth() * getHeight() * 4); + byteBuffer = new byte[getWidth() * getHeight() * 4]; + prevImageByteBuffer = new byte[getWidth() * getHeight() * 4]; + imageByteBuffer = new int[getWidth() * getHeight()]; + pixelWriter = getGraphics(destination); + + this.component = destination; + + } + + /** + * Initialize the component renderer. + * @param renderer the JMonkey {@link Renderer renderer} to use. + * @param main true if the attached frame buffer is the main one or false otherwise. + */ + public void init(Renderer renderer, boolean main) { + if (main) { + renderer.setMainFrameBufferOverride(frameBuffer); + } + } + + /** + * Get the graphics context of the given component. + * @param destination the AWT component used for rendering. + * @return the graphics context of the given component. + */ + protected Graphics getGraphics(Component destination) { + if (destination != null) { + + if (destination.getGraphics() != null) { + return destination.getGraphics(); + } else { + System.out.println("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities."); + return null; + //throw new IllegalArgumentException("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities."); + } + } else { + throw new NullPointerException("Component cannot be null."); + } + } + + /** + * Get the width of the area to render. + * @return the width of the area to render. + * @see #getHeight() + */ + public int getWidth() { + return width; + } + + /** + * Get the height of the area to render. + * @return the height of the area to render. + * @see #getWidth() + */ + public int getHeight() { + return height; + } + + /** + * Copy the JMonkey frame buffer that has been rendered by the JMonkey engine and schedule the rendering of the component. + * @param renderManager the JMonkey render manager. + */ + public void copyFrameBufferToImage(RenderManager renderManager) { + + while (!frameState.compareAndSet(WAITING_STATE, RUNNING_STATE)) { + if (frameState.get() == DISPOSED_STATE) { + return; + } + } + + // Convert screenshot. + try { + + frameByteBuffer.clear(); + + final Renderer renderer = renderManager.getRenderer(); + renderer.readFrameBufferWithFormat(frameBuffer, frameByteBuffer, Image.Format.RGBA8); + + } finally { + if (!frameState.compareAndSet(RUNNING_STATE, WAITING_STATE)) { + throw new RuntimeException("unknown problem with the frame state"); + } + } + + synchronized (byteBuffer) { + frameByteBuffer.get(byteBuffer); + + if (transferMode == TransferMode.ON_CHANGES) { + + final byte[] prevBuffer = getPrevImageByteBuffer(); + + if (Arrays.equals(prevBuffer, byteBuffer)) { + if (frameCount == 0) return; + } else { + frameCount = 2; + System.arraycopy(byteBuffer, 0, prevBuffer, 0, byteBuffer.length); + } + + frameByteBuffer.position(0); + frameCount--; + } + } + + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + writeFrame(); + }}); + } + + /** + * Write the current rendered frame to the component graphics contex. + */ + protected void writeFrame() { + + + if (pixelWriter != null) { + while (!imageState.compareAndSet(WAITING_STATE, RUNNING_STATE)) { + if (imageState.get() == DISPOSED_STATE) { + return; + } + } + + try { + + final int[] imageDataBuffer = getImageByteBuffer(); + + synchronized (byteBuffer) { + + for(int i = 0; i < width * height; i++) { + imageDataBuffer[i] = ((0xff & byteBuffer[i*4+3]) << 24) // Alpha + | ((0xff & byteBuffer[i*4]) << 16) // Red + | ((0xff & byteBuffer[i*4+1]) << 8) // Green + | ((0xff & byteBuffer[i*4+2])); // BLue + + } + } + + DataBuffer buffer = new DataBufferInt(imageDataBuffer, imageDataBuffer.length); + + SampleModel sm = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, getWidth(), getHeight(), new int[] {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}); + + WritableRaster raster = Raster.createWritableRaster(sm, buffer, null); + + BufferedImage img = new BufferedImage(colorModel, raster, false, null); + + BufferedImage img2 = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); + offGraphics = img2.createGraphics(); + offGraphics.setColor(component.getBackground()); + img2.createGraphics().fillRect(0, 0, getWidth(), getHeight()); + img2.createGraphics().drawImage(img, null, null); + + component.getGraphics().drawImage(img2, 0, 0, null); + + } finally { + if (!imageState.compareAndSet(RUNNING_STATE, WAITING_STATE)) { + throw new RuntimeException("unknown problem with the image state"); + } + } + } else { + System.out.println("No graphics context available for rendering."); + } + + + } + + /** + * Get the image byte buffer. + * @return the image byte buffer. + */ + protected int[] getImageByteBuffer() { + return imageByteBuffer; + } + + /** + * Get the previous image byte buffer. + * @return the previous image byte buffer. + */ + protected byte[] getPrevImageByteBuffer() { + return prevImageByteBuffer; + } + + /** + * Dispose this renderer. The underlying frame buffer is also disposed. + */ + public void dispose() { + while (!frameState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ; + while (!imageState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ; + frameBuffer.dispose(); + BufferUtils.destroyDirectBuffer(frameByteBuffer); + frameState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); + imageState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java new file mode 100644 index 000000000..78cde8f74 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + + +import com.jme3.input.AWTKeyInput; +import com.jme3.input.AWTMouseInput; +import com.jme3.input.JoyInput; +import com.jme3.input.TouchInput; +import com.jme3.opencl.Context; +import com.jme3.renderer.Renderer; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.JmeSystem; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; + +/** + * A JMonkey {@link JmeContext context} that is dedicated to AWT component rendering. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTContext implements JmeContext { + + /** + * The settings. + */ + protected final AppSettings settings; + + /** + * The key input. + */ + protected final AWTKeyInput keyInput; + + /** + * The mouse input. + */ + protected final AWTMouseInput mouseInput; + + /** + * The current width. + */ + private volatile int width; + + /** + * The current height. + */ + private volatile int height; + + /** + * The background context. + */ + protected JmeContext backgroundContext; + + public AWTContext() { + this.keyInput = new AWTKeyInput(this); + this.mouseInput = new AWTMouseInput(this); + this.settings = createSettings(); + this.backgroundContext = createBackgroundContext(); + this.height = 1; + this.width = 1; + } + + /** + * @return the current height. + */ + public int getHeight() { + return height; + } + + /** + * @param height the current height. + */ + public void setHeight(final int height) { + this.height = height; + } + + /** + * @return the current width. + */ + public int getWidth() { + return width; + } + + /** + * @param width the current width. + */ + public void setWidth(final int width) { + this.width = width; + } + + /** + * @return new settings. + */ + protected AppSettings createSettings() { + final AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL3); + return settings; + } + + /** + * @return new context/ + */ + protected JmeContext createBackgroundContext() { + return JmeSystem.newContext(settings, Type.OffscreenSurface); + } + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL3); + this.backgroundContext.setSettings(settings); + } + + @Override + public void setSystemListener(final SystemListener listener) { + backgroundContext.setSystemListener(listener); + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public Renderer getRenderer() { + return backgroundContext.getRenderer(); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public AWTMouseInput getMouseInput() { + return mouseInput; + } + + @Override + public AWTKeyInput getKeyInput() { + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public Timer getTimer() { + return backgroundContext.getTimer(); + } + + @Override + public void setTitle(final String title) { + } + + @Override + public boolean isCreated() { + return backgroundContext != null && backgroundContext.isCreated(); + } + + @Override + public boolean isRenderable() { + return backgroundContext != null && backgroundContext.isRenderable(); + } + + @Override + public void setAutoFlushFrames(final boolean enabled) { + // TODO Auto-generated method stub + } + + @Override + public void create(final boolean waitFor) { + String render = System.getProperty("awt.background.render", AppSettings.LWJGL_OPENGL33); + backgroundContext.getSettings().setRenderer(render); + backgroundContext.create(waitFor); + } + + @Override + public void restart() { + } + + @Override + public void destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + backgroundContext.destroy(waitFor); +} + +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java new file mode 100644 index 000000000..81dc7b029 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Component; +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import com.jme3.app.Application; +import com.jme3.input.AWTKeyInput; +import com.jme3.input.AWTMouseInput; +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; + +/** + * A frame processor that enables to render JMonkey frame buffer onto an AWT component. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTFrameProcessor implements SceneProcessor, PropertyChangeListener { + + public enum TransferMode { + ALWAYS, + ON_CHANGES + } + + private Application application = null; + + /** + * The width listener. + */ + protected PropertyChangeListener widthListener; + + /** + * The height listener. + */ + protected PropertyChangeListener heightListener; + + /** + * The ration listener. + */ + protected PropertyChangeListener rationListener; + + /** + * The flag to decide when we should resize. + */ + private final AtomicInteger reshapeNeeded; + + /** + * The render manager. + */ + private RenderManager renderManager; + + /** + * The source view port. + */ + private ViewPort viewPort; + + /** + * The frame transfer. + */ + private AWTComponentRenderer frameTransfer; + + /** + * The transfer mode. + */ + private TransferMode transferMode; + + /** + * The destination of jMe frames. + */ + protected volatile Component destination; + + /** + * The flag is true if this processor is main. + */ + private volatile boolean main; + + private int askWidth; + private int askHeight; + + private boolean askFixAspect; + private boolean enabled; + + @Override + public void initialize(RenderManager rm, ViewPort vp) { + this.renderManager = rm; + } + + @Override + public void reshape(ViewPort vp, int w, int h) { + // TODO Auto-generated method stub + } + + @Override + public boolean isInitialized() { + return frameTransfer != null; + } + + @Override + public void preFrame(float tpf) { + // TODO Auto-generated method stub + + } + + @Override + public void postQueue(RenderQueue rq) { + // TODO Auto-generated method stub + + } + + @Override + public void postFrame(FrameBuffer out) { + if (!isEnabled()) { + return; + } + + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + if (frameTransfer != null) { + frameTransfer.copyFrameBufferToImage(getRenderManager()); + } + + // for the next frame + if (hasDestination() && reshapeNeeded.get() > 0 && reshapeNeeded.decrementAndGet() >= 0) { + + if (frameTransfer != null) { + frameTransfer.dispose(); + } + + setFrameTransfer(reshapeInThread(askWidth, askHeight, askFixAspect)); + } + } + + @Override + public void cleanup() { + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + + if (frameTransfer != null) { + frameTransfer.dispose(); + setFrameTransfer(null); + } + } + + @Override + public void setProfiler(AppProfiler profiler) { + // TODO Auto-generated method stub + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + System.out.println("Property changed: "+evt.getPropertyName()+" "+evt.getOldValue()+" -> "+evt.getNewValue()); + } + + public AWTFrameProcessor() { + transferMode = TransferMode.ALWAYS; + askWidth = 1; + askHeight = 1; + main = true; + reshapeNeeded = new AtomicInteger(2); + } + + /** + * Notify about that the ratio was changed. + * + * @param newValue the new value of the ratio. + */ + protected void notifyChangedRatio(Boolean newValue) { + notifyComponentResized(destination.getWidth(), destination.getHeight(), newValue); + } + + /** + * Notify about that the height was changed. + * + * @param newValue the new value of the height. + */ + protected void notifyChangedHeight(Number newValue) { + notifyComponentResized(destination.getWidth(), newValue.intValue(), isPreserveRatio()); + } + + /** + * Notify about that the width was changed. + * + * @param newValue the new value of the width. + */ + protected void notifyChangedWidth(Number newValue) { + notifyComponentResized(newValue.intValue(), destination.getHeight(), isPreserveRatio()); + } + + /** + * Gets the application. + * + * @return the application. + */ + protected Application getApplication() { + return application; + } + + /** + * Sets the application. + * + * @param application the application. + */ + protected void setApplication(Application application) { + this.application = application; + } + + /** + * Gets the current destination. + * + * @return the current destination. + */ + protected Component getDestination() { + return destination; + } + + /** + * Sets the destination. + * + * @param destination the destination. + */ + protected void setDestination(Component destination) { + this.destination = destination; + } + + /** + * Checks of existing destination. + * @return true if destination is exists. + */ + protected boolean hasDestination() { + return destination != null; + } + + /** + * Checks of existing application. + * @return true if destination is exists. + */ + protected boolean hasApplication() { + return application != null; + } + + + /** + * Gets the frame transfer. + * @return the file transfer. + */ + protected AWTComponentRenderer getFrameTransfer() { + return frameTransfer; + } + + /** + * Sets the frame transfer. + * + * @param frameTransfer the file transfer. + */ + protected void setFrameTransfer(AWTComponentRenderer frameTransfer) { + this.frameTransfer = frameTransfer; + } + + /** + * Gets the view port. + * + * @return the view port. + */ + protected ViewPort getViewPort() { + return viewPort; + } + + /** + * Gets the render manager. + * + * @return the render manager. + */ + protected RenderManager getRenderManager() { + return renderManager; + } + + public boolean isMain() { + return main; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Handle resizing. + * + * @param newWidth the new width. + * @param newHeight the new height. + * @param fixAspect true if need to fix aspect. + */ + protected void notifyComponentResized(int newWidth, int newHeight, boolean fixAspect) { + + newWidth = Math.max(newWidth, 1); + newHeight = Math.max(newHeight, 1); + + if (askWidth == newWidth && askWidth == newHeight && askFixAspect == fixAspect) { + return; + } + + askWidth = newWidth; + askHeight = newHeight; + askFixAspect = fixAspect; + reshapeNeeded.set(2); + } + + public void reshape() { + reshapeNeeded.set(2); + } + + /** + * Is preserve ratio. + * + * @return is preserve ratio. + */ + protected boolean isPreserveRatio() { + return false; + } + + /** + * Gets destination width. + * + * @return the destination width. + */ + protected int getDestinationWidth() { + return getDestination().getWidth(); + } + + /** + * Gets destination height. + * + * @return the destination height. + */ + protected int getDestinationHeight() { + return getDestination().getHeight(); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + */ + public void bind(Component destination, Application application) { + final RenderManager renderManager = application.getRenderManager(); + + if (renderManager == null) { + throw new RuntimeException("No render manager available from the application."); + } + + List postViews = renderManager.getPostViews(); + if (postViews.isEmpty()) { + throw new RuntimeException("the list of a post view is empty."); + } + + bind(destination, application, postViews.get(postViews.size() - 1), true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param viewPort the view port. + */ + public void bind(Component destination, Application application, ViewPort viewPort) { + bind(destination, application, viewPort, true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param inputNode the input node. + * @param viewPort the view port. + * @param main true if this processor is main. + */ + public void bind(final Component destination, final Application application, ViewPort viewPort, boolean main) { + + if (hasApplication()) { + throw new RuntimeException("This process is already bonded."); + } + + setApplication(application); + setEnabled(true); + + this.main = main; + this.viewPort = viewPort; + this.viewPort.addProcessor(this); + + if (EventQueue.isDispatchThread()) { + bindDestination(application, destination); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + bindDestination(application, destination); + }}); + } + } + + /** + * Unbind this processor from its current destination. + */ + public void unbind() { + + if (viewPort != null) { + viewPort.removeProcessor(this); + viewPort = null; + } + + if (EventQueue.isDispatchThread()) { + unbindDestination(); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + unbindDestination(); + }}); + } + + } + + /** + * Bind this processor. + * + * @param application the application. + * @param destination the destination. + * @param inputNode the input node. + */ + protected void bindDestination(Application application, Component destination) { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("bind has to be done from the Event Dispatching thread."); + } + + if (isMain()) { + + if (application.getContext() != null) { + if (application.getContext() instanceof AWTContext) { + AWTContext context = (AWTContext) application.getContext(); + AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.bind(destination); + AWTKeyInput keyInput = context.getKeyInput(); + keyInput.bind(destination); + + setDestination(destination); + bindListeners(); + + notifyComponentResized(getDestinationWidth(), getDestinationHeight(), isPreserveRatio()); + + } else { + throw new IllegalArgumentException("Underlying application has to use AWTContext (actually using "+application.getContext().getClass().getSimpleName()+")"); + } + } else { + throw new IllegalArgumentException("Underlying application has to use a valid AWTContext (context is null)"); + } + } + } + + /** + * Unbind this processor from destination. + */ + protected void unbindDestination() { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("unbind has to be done from the Event Dispatching thread."); + } + + if (hasApplication() && isMain()) { + final AWTContext context = (AWTContext) getApplication().getContext(); + final AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.unbind(); + final AWTKeyInput keyInput = context.getKeyInput(); + keyInput.unbind(); + } + + setApplication(null); + + if (hasDestination()) { + unbindListeners(); + setDestination(null); + } + } + + + protected void bindListeners() { + Component destination = getDestination(); + destination.addPropertyChangeListener(this); + destination.addPropertyChangeListener(this); + } + + + protected void unbindListeners() { + Component destination = getDestination(); + destination.removePropertyChangeListener(this); + destination.removePropertyChangeListener(this); + } + + /** + * Reshape the current frame transfer for the new size. + * + * @param width the width. + * @param height the height. + * @param fixAspect true if need to fix aspect ration. + * @return the new frame transfer. + */ + protected AWTComponentRenderer reshapeInThread(final int width, final int height, final boolean fixAspect) { + + reshapeCurrentViewPort(width, height); + + ViewPort viewPort = getViewPort(); + RenderManager renderManager = getRenderManager(); + FrameBuffer frameBuffer = viewPort.getOutputFrameBuffer(); + + AWTComponentRenderer frameTransfer = createFrameTransfer(frameBuffer, width, height); + frameTransfer.init(renderManager.getRenderer(), isMain()); + + if (isMain()) { + AWTContext context = (AWTContext) getApplication().getContext(); + context.setHeight(height); + context.setWidth(width); + } + + return frameTransfer; + } + + /** + * Create a new frame transfer. + * + * @param frameBuffer the frame buffer. + * @param width the width. + * @param height the height. + * @return the new frame transfer. + */ + protected AWTComponentRenderer createFrameTransfer(FrameBuffer frameBuffer, int width, int height) { + return new AWTComponentRenderer(getDestination(), getTransferMode(), isMain() ? null : frameBuffer, width, height); + } + + /** + * Reshape the current view port. + * + * @param width the width. + * @param height the height. + */ + protected void reshapeCurrentViewPort(int width, int height) { + + ViewPort viewPort = getViewPort(); + Camera camera = viewPort.getCamera(); + int cameraAngle = getCameraAngle(); + float aspect = (float) camera.getWidth() / camera.getHeight(); + + if (isMain()) { + getRenderManager().notifyReshape(width, height); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + return; + } + + camera.resize(width, height, true); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + + final List processors = viewPort.getProcessors(); + + boolean found = false; + Iterator iter = processors.iterator(); + while(!found && iter.hasNext()) { + if (!(iter.next() instanceof AWTFrameProcessor)) { + found = true; + } + } + + if (found) { + + FrameBuffer frameBuffer = new FrameBuffer(width, height, 1); + frameBuffer.setDepthBuffer(Image.Format.Depth); + frameBuffer.setColorBuffer(Image.Format.RGBA8); + frameBuffer.setSrgb(true); + + viewPort.setOutputFrameBuffer(frameBuffer); + } + + for (final SceneProcessor sceneProcessor : processors) { + if (!sceneProcessor.isInitialized()) { + sceneProcessor.initialize(renderManager, viewPort); + } else { + sceneProcessor.reshape(viewPort, width, height); + } + } + } + + /** + * Gets camera angle. + * + * @return the camera angle. + */ + protected int getCameraAngle() { + final String angle = System.getProperty("awt.frame.transfer.camera.angle", "45"); + return Integer.parseInt(angle); + } + + public TransferMode getTransferMode() { + return transferMode; + } + + public void setTransferMode(TransferMode transferMode) { + this.transferMode = transferMode; + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java new file mode 100644 index 000000000..07e3025b8 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + *

+ * This class is dedicated to the queuing of AWT related tasks and their execution. + * It's enable to store tasks that have to be executed within an AWT context and execute them at the specified time. + *

+ *

+ * This class is an AWT implementation of the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTTaskExecutor { + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final AWTTaskExecutor INSTANCE = new AWTTaskExecutor(); + + /** + * Get the instance of the executor. + * @return the instance of executor. + */ + public static AWTTaskExecutor getInstance() { + return INSTANCE; + } + + /** + * The list of waiting tasks. + */ + private final List waitTasks; + + private AWTTaskExecutor() { + waitTasks = new LinkedList(); + } + + public List getWaitingTasks(){ + return waitTasks; + } + + /** + * Add the given {@link Runnable runnable} to the list of planned executions. + * @param task the task to add. + * @see #execute() + */ + public void addToExecute(final Runnable task) { + lock.writeLock().lock(); + try { + waitTasks.add(task); + } catch (Exception e) { + // This try catch block enable to free the lock in case of any unexpected error. + } + lock.writeLock().unlock(); + } + + /** + * Execute all the tasks that are waiting. + * @see #addToExecute(Runnable) + */ + public void execute() { + + if (waitTasks.isEmpty()) return; + + lock.readLock().lock(); + + try { + for(Runnable runnable : waitTasks) { + runnable.run(); + } + } catch (Exception e) { + // This try catch block enable to free the lock in case of any unexpected error. + } + + waitTasks.clear(); + + lock.readLock().unlock(); + } +}