In my unreleased MOSS stuff, I have a Swing app that has a JSplitPane with a JSplitPane in it because I wanted a center JME window with Swing controls to the left and right. I remember something about the event updating and doing things a frame later or something. Anyway, here is the source code for the relevant parts.
Hopefully you can read around the app-specific parts and see something different that I’m doing.
Main.java:
/*
* $Id$
*
* Copyright (c) 2020, Simsilica, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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.simsilica.mapper;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import org.slf4j.*;
import org.pushingpixels.substance.api.skin.SubstanceGraphiteGlassLookAndFeel;
import com.jme3.app.*;
import com.jme3.app.state.ScreenshotAppState;
import com.jme3.input.*;
import com.jme3.light.*;
import com.jme3.material.*;
import com.jme3.math.*;
import com.jme3.scene.*;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanelsContext;
import com.simsilica.fx.LightingState;
import com.simsilica.fx.sky.SkyState;
import com.simsilica.fx.sky.SkySettingsState;
import com.simsilica.lemur.*;
import com.simsilica.lemur.input.*;
import com.simsilica.input.*;
import com.simsilica.state.*;
import com.simsilica.thread.*;
/**
*
*
* @author Paul Speed
*/
public class Main extends SimpleApplication {
static Logger log = LoggerFactory.getLogger(Main.class);
private volatile JFrame mainFrame;
public static void main( String... args ) throws Exception {
JFrame.setDefaultLookAndFeelDecorated(true);
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
UIManager.setLookAndFeel(new SubstanceGraphiteGlassLookAndFeel());
final Main app = new Main();
app.setShowSettings(false);
AppSettings settings = new AppSettings(true);
settings.setCustomRenderer(AwtPanelsContext.class);
settings.setFrameRate(30);
settings.setGammaCorrection(false);
app.setSettings(settings);
app.start();
}
public Main() throws Exception {
super(new StatsAppState(), new DebugKeysAppState(), new BasicProfilerState(false),
new DebugHudState(), // from SiO2
new MemoryDebugState(), // from SiO2
new JobState(null, 4, -1), // from SiO2
new CameraState(70, 0.1f, 8000), // from SiO2
//new MovementState(), // from SiO2
new CameraControlState(),
new LightingState(), // from SimFX
new SkyState(true), // from SimFX
new SkySettingsState(), // from SimFX
new HudState(),
new PostProcessingState(),
new CubeSceneState(ColorRGBA.Blue, false), // from SiO2
new MapState(),
new MapViewState(),
new AvatarState(),
new ModelState(),
new MarkerState(),
new ScriptState()
);
stateManager.attach(new ScreenshotAppState("", System.currentTimeMillis()) {
@Override
protected void writeImageFile( final java.io.File file ) throws java.io.IOException {
super.writeImageFile(file);
System.out.println("Wrote file:" + file);
}
});
//float masterScale = 1024f/32768;
//stateManager.getState(CubeSceneState.class).getScene().setLocalScale(0.5f * masterScale, masterScale, 0.5f * masterScale);
//stateManager.getState(CubeSceneState.class).getScene().setLocalScale(20);
stateManager.getState(CubeSceneState.class).getScene().setLocalScale(0);
// Have to create the frame on the AWT EDT.
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
mainFrame = new JFrame("Mapper v1.0.0");
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
stop();
}
});
mainFrame.setJMenuBar(createMainMenu());
JSplitPane split = new JSplitPane();
split.setContinuousLayout(false);
split.setBackground(Color.black);
JPanel left = new JPanel();
JLabel testLabel = new JLabel("Testing");
JLabel testLabel2 = new JLabel("Testing2");
left.add(testLabel);
//left.add(testLabel2);
split.add(left, JSplitPane.LEFT);
mainFrame.getContentPane().add(split, BorderLayout.CENTER);
// For the right panel, we'll split it again so we can have a property
// pane on the right-right.
JSplitPane rightSplit = new JSplitPane();
split.add(rightSplit, JSplitPane.RIGHT);
rightSplit.setContinuousLayout(false);
rightSplit.setBackground(Color.black);
//PropertyEditorPanel objectEditor = new PropertyEditorPanel(gui, "ui.editor");
//objectEditor.setPreferredSize(new Dimension(250, 100));
rightSplit.add(testLabel2, JSplitPane.RIGHT);
stateManager.attach(new AwtPanelState(rightSplit, JSplitPane.LEFT));
//stateManager.attach(new MovementState());
}
});
stateManager.getState(AwtPanelState.class).addEnabledCommand(new Runnable() {
public void run() {
if( !mainFrame.isVisible() ) {
// By now we should have the panel inside
mainFrame.pack();
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
// Just in case
GuiGlobals.getInstance().setCursorEventsEnabled(true);
inputManager.setCursorVisible(true);
}
}
});
}
@Override
public void simpleInitApp() {
log.info("simpleInitapp()");
// Because we will use Lemur for some things... go ahead and setup
// the very basics
GuiGlobals.initialize(this);
//GuiGlobals.getInstance().setCursorEventsEnabled(true);
MapperConfig.getInstance();
cam.setLocation(new Vector3f(0, 1000, 0));
cam.lookAt(new Vector3f(), Vector3f.UNIT_Z.mult(-1));
stateManager.getState(LightingState.class).setTimeOfDay(0.2f);
stateManager.getState(LightingState.class).setOrientation(0.5f);
LightingState lighting = stateManager.getState(LightingState.class);
lighting.setSunColor(ColorRGBA.White.clone());
lighting.setAmbient(ColorRGBA.White.clone().mult(0.75f));
// The swing->JME interface swallows the print screen.
// (in fact, even through AWT we only get the release event)
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventDispatcher(new KeyEventDispatcher() {
@Override
public boolean dispatchKeyEvent( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_PRINTSCREEN ) {
// We only get the release event but we'll check
// for it anyway
if( e.getID() == KeyEvent.KEY_RELEASED ) {
takeScreenShot();
}
}
return false;
}
});
}
public void takeScreenShot() {
stateManager.getState(ScreenshotAppState.class).takeScreenshot();
}
@Override
public void simpleUpdate( float tpf ) {
}
private JMenuBar createMainMenu() {
return new JMenuBar();
}
}
AwtPanelState.java
/*
* $Id$
*
* Copyright (c) 2016, Simsilica, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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.simsilica.mapper;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.*;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
/**
*
*
* @author Paul Speed
*/
public class AwtPanelState extends BaseAppState {
// Other than initial setup these fields are _only_ accessed
// from the swing thread except Viewport attachment
private final Container container;
private final Object constraints;
private volatile AwtPanel panel;
private List<Runnable> enabledCommands = new CopyOnWriteArrayList<>();
public AwtPanelState( Container container, Object constraints ) {
this.container = container;
this.constraints = constraints;
}
public void addEnabledCommand( Runnable cmd ) {
enabledCommands.add(cmd);
}
protected void initialize( Application app ) {
try {
SwingUtilities.invokeAndWait(new InitializeCommand());
} catch( InterruptedException | InvocationTargetException e ) {
throw new RuntimeException("Error creating panel on swing thread", e);
}
// Can't unattach them so we might as well do it on init
panel.attachTo(true, app.getViewPort(), app.getGuiViewPort());
}
protected void onEnable() {
SwingUtilities.invokeLater(new AttachPanelCommand());
}
protected void onDisable() {
SwingUtilities.invokeLater(new DetachPanelCommand());
}
protected void cleanup( Application app ) {
}
private class InitializeCommand implements Runnable {
public void run() {
AwtPanelsContext ctx = (AwtPanelsContext)getApplication().getContext();
panel = ctx.createPanel(PaintMode.Accelerated);
panel.setPreferredSize(new Dimension(1280, 720));
panel.setMinimumSize(new Dimension(400, 300));
panel.setBackground(Color.black);
ctx.setInputSource(panel);
}
}
private class AttachPanelCommand implements Runnable {
public void run() {
// Add it to the container provided
container.add(panel, constraints);
for( Runnable r : enabledCommands ) {
r.run();
}
}
}
private class DetachPanelCommand implements Runnable {
public void run() {
container.remove(panel);
}
}
}