Use ImGui in version 3.10.0
Vlog 03: Integrating Dear ImGui (1.92.0+) with latest jME3: Initial Findings, Traps, and Architecture Choices General Discussion
Hi everyone, I am currently running some integration tests between the latest versions of the imgui-java wrapper (specifically targeting Dear ImGui 1.92.0+) and jMonkeyEngine 3.10.0+. My goal is to build a robust, desktop-targeted UI integration layer. During my initial research and implementation phases, I encountered a few architectural shifts in both ecosystems that drastically change how we used to integrate ImGui in the past. I wanted to share my current findings with the community as a …
Reference content
Added the controller section
package com.jmeimgui;
import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystem;
import com.jme3.system.Platform;
import com.jme3.system.lwjgl.LwjglWindow;
import com.jme3.input.InputManager;
import com.jme3.input.RawInputListener;
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.input.event.JoyButtonEvent;
import com.jme3.input.event.JoyAxisEvent;
import imgui.ImDrawData;
import imgui.ImGui;
import imgui.ImGuiIO;
import imgui.flag.ImGuiConfigFlags;
import imgui.flag.ImGuiKey;
import imgui.gl3.ImGuiImplGl3;
import imgui.type.ImInt;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.GL_VERSION;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL11.glGetString;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11C.GL_TEXTURE_MIN_FILTER;
/**
* JME3与ImGui集成类 - 已适配 JME 3.10.0-alpha5 及 LWJGL 3.4.1 SDL3 后端
* @author Icyboxs, Gemini
*/
public class JmeImGui {
private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3();
private long windowHandle;
private boolean initialized = false;
// 【关键修改点 1】:新增成员变量,用来就地缓存初始化时喂进来的上下文句柄
private JmeContext cachedContext;
private InputManager inputManager;
private final List<KeyEventEntry> keyEventQueue = new ArrayList<>();
private final List<MouseButtonEntry> mouseButtonQueue = new ArrayList<>();
private float mousePosX = -1f;
private float mousePosY = -1f;
private float mouseWheelH = 0f;
private float mouseWheelV = 0f;
private boolean ctrlDown = false;
private boolean shiftDown = false;
private boolean altDown = false;
private boolean superDown = false;
private boolean windowFocused = true;
private static class KeyEventEntry {
final int keyCode;
final char keyChar;
final boolean pressed;
final boolean repeating;
KeyEventEntry(int keyCode, char keyChar, boolean pressed, boolean repeating) {
this.keyCode = keyCode;
this.keyChar = keyChar;
this.pressed = pressed;
this.repeating = repeating;
}
}
private static class MouseButtonEntry {
final int button;
final boolean pressed;
MouseButtonEntry(int button, boolean pressed) {
this.button = button;
this.pressed = pressed;
}
}
/**
* 初始化ImGui - 适配1.92.0+ 与 SDL 窗口上下文(无InputManager,兼容旧代码)
*/
public void init(JmeContext context, Runnable beforeInit) {
init(context, null, beforeInit);
}
public void init(JmeContext context) {
init(context, null, null);
}
public void init(JmeContext context, InputManager inputManager) {
init(context, inputManager, null);
}
/**
* 初始化ImGui - 完整版,接收InputManager以转发输入事件
*/
public void init(JmeContext context, InputManager inputManager, Runnable beforeInit) {
this.cachedContext = context;
this.windowHandle = ((LwjglWindow) context).getWindowHandle();
this.inputManager = inputManager;
ImGui.createContext();
ImGuiIO io = ImGui.getIO();
io.setIniFilename(null);
io.addConfigFlags(ImGuiConfigFlags.DockingEnable);
if (beforeInit != null) {
beforeInit.run();
}
try {
io.setBackendPlatformName("imgui_impl_sdl3");
io.setBackendRendererName("imgui_impl_opengl3");
} catch (NoSuchMethodError e) {
}
if (inputManager != null) {
setupInputListener();
System.out.println("[JmeImGui] 输入监听器已注册,ImGui可正常接收键鼠事件");
} else {
System.out.println("[JmeImGui] 警告:未提供InputManager,ImGui将无法响应键鼠输入");
}
imGuiGl3.init(decideGlslVersion());
imGuiGl3.newFrame();
initialized = true;
}
public void startFrame() {
if (!initialized) return;
imgui.ImGuiIO io = ImGui.getIO();
try {
if (cachedContext != null && cachedContext.getSettings() != null) {
com.jme3.system.AppSettings settings = cachedContext.getSettings();
io.setDisplaySize((float) settings.getWidth(), (float) settings.getHeight());
if (cachedContext.getTimer() != null) {
float tpf = cachedContext.getTimer().getTimePerFrame();
io.setDeltaTime(tpf > 0 ? tpf : 1.0f / 60.0f);
} else {
io.setDeltaTime(1.0f / 60.0f);
}
} else {
io.setDisplaySize(1920f, 1080f);
io.setDeltaTime(1.0f / 60.0f);
}
} catch (Exception e) {
io.setDisplaySize(1920f, 1080f);
io.setDeltaTime(1.0f / 60.0f);
}
// 转发 JME3 输入事件到 ImGuiIO
processInput();
ImGui.newFrame();
}
/**
* 结束帧并渲染
*/
public void endFrame() {
if (!initialized || ImGui.getCurrentContext() == null) {
return;
}
try {
ImGui.render(); //
final ImDrawData drawData = ImGui.getDrawData(); //
if (!isValidDrawData(drawData)) { //
return;
}
// 渲染主视口
imGuiGl3.renderDrawData(drawData); //
} catch (Exception e) {
System.err.println("ImGui渲染错误: " + e.getClass().getSimpleName());
e.printStackTrace();
}
}
/**
* 验证绘制数据有效性
*/
private boolean isValidDrawData(ImDrawData drawData) {
if (drawData == null) { //
return false;
}
if (!drawData.getValid()) { //
return false;
}
try {
float width = drawData.getDisplaySizeX(); //
float height = drawData.getDisplaySizeY(); //
return width > 0 && height > 0; //
} catch (Exception e) {
return false;
}
}
/**
* 刷新字体纹理
*/
public void refreshFontTexture() {
if (!initialized) return;
ImInt fontW = new ImInt(); //
ImInt fontH = new ImInt(); //
ImGuiIO imGuiIO = ImGui.getIO(); //
ByteBuffer fontData = imGuiIO.getFonts().getTexDataAsRGBA32(fontW, fontH); //
int originalTexture = glGetInteger(GL_TEXTURE_BINDING_2D); //
int fontTexture = glGenTextures(); //
glBindTexture(GL_TEXTURE_2D, fontTexture); //
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fontW.get(), fontH.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, fontData); //
imGuiIO.getFonts().setTexID(fontTexture); //
glBindTexture(GL_TEXTURE_2D, originalTexture); //
}
/**
* 释放资源
*/
public void dispose() {
if (!initialized) return;
try {
ImGui.destroyContext(); //
initialized = false;
System.out.println("ImGui资源已释放");
} catch (Exception e) {
System.err.println("ImGui资源释放错误: " + e.getMessage());
}
}
private void setupInputListener() {
if (inputManager == null) return;
inputManager.addRawInputListener(new RawInputListener() {
@Override public void beginInput() {}
@Override public void endInput() {}
@Override
public void onKeyEvent(KeyInputEvent evt) {
keyEventQueue.add(new KeyEventEntry(
evt.getKeyCode(), evt.getKeyChar(),
evt.isPressed(), evt.isRepeating()
));
}
@Override
public void onMouseButtonEvent(MouseButtonEvent evt) {
mousePosX = (float) evt.getX();
mousePosY = (float) evt.getY();
int btn = evt.getButtonIndex();
if (btn < 3) {
mouseButtonQueue.add(new MouseButtonEntry(btn, evt.isPressed()));
}
}
@Override
public void onMouseMotionEvent(MouseMotionEvent evt) {
mousePosX = (float) evt.getX();
mousePosY = (float) evt.getY();
if (evt.getDeltaWheel() != 0) {
mouseWheelV += evt.getDeltaWheel() / 120f;
}
}
@Override public void onTouchEvent(TouchEvent evt) {}
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
});
}
/**
* 将JME3 RawInputListener捕获的事件批量转发到ImGuiIO
* 必须在每帧 ImGui.newFrame() 前调用
*/
private void processInput() {
ImGuiIO io = ImGui.getIO();
io.addFocusEvent(windowFocused);
// 鼠标位置 (JME3 Y=0在底部 → ImGui Y=0在顶部,需翻转)
if (mousePosX >= 0 && mousePosY >= 0) {
io.addMousePosEvent(mousePosX, io.getDisplaySizeY() - mousePosY);
}
// 滚轮
if (mouseWheelH != 0f || mouseWheelV != 0f) {
io.addMouseWheelEvent(mouseWheelH, mouseWheelV);
mouseWheelH = 0f;
mouseWheelV = 0f;
}
// 鼠标按键
for (int i = 0; i < mouseButtonQueue.size(); i++) {
MouseButtonEntry e = mouseButtonQueue.get(i);
io.addMouseButtonEvent(e.button, e.pressed);
}
mouseButtonQueue.clear();
// 键盘事件
for (int i = 0; i < keyEventQueue.size(); i++) {
KeyEventEntry e = keyEventQueue.get(i);
int imKey = mapJmeKeyToImGuiKey(e.keyCode);
if (imKey >= 0) {
io.addKeyEvent(imKey, e.pressed);
// 跟踪修饰键状态
if (e.keyCode == 42 || e.keyCode == 54) {
shiftDown = e.pressed;
} else if (e.keyCode == 29 || e.keyCode == 157) {
ctrlDown = e.pressed;
} else if (e.keyCode == 56 || e.keyCode == 184) {
altDown = e.pressed;
} else if (e.keyCode == 219 || e.keyCode == 220) {
superDown = e.pressed;
}
}
// 文本输入
if (e.pressed && e.keyChar != 0 && e.keyChar != '\0') {
io.addInputCharacter(e.keyChar);
}
}
keyEventQueue.clear();
// 同步修饰键状态
io.setKeyCtrl(ctrlDown);
io.setKeyShift(shiftDown);
io.setKeyAlt(altDown);
io.setKeySuper(superDown);
}
/**
* JME3 KeyInput 键码 → ImGuiKey 键码映射
*/
@SuppressWarnings("MagicNumber")
private static int mapJmeKeyToImGuiKey(int jmeKey) {
switch (jmeKey) {
// 字母键
case 30: return ImGuiKey.A; case 48: return ImGuiKey.B;
case 46: return ImGuiKey.C; case 32: return ImGuiKey.D;
case 18: return ImGuiKey.E; case 33: return ImGuiKey.F;
case 34: return ImGuiKey.G; case 35: return ImGuiKey.H;
case 23: return ImGuiKey.I; case 36: return ImGuiKey.J;
case 37: return ImGuiKey.K; case 38: return ImGuiKey.L;
case 50: return ImGuiKey.M; case 49: return ImGuiKey.N;
case 24: return ImGuiKey.O; case 25: return ImGuiKey.P;
case 16: return ImGuiKey.Q; case 19: return ImGuiKey.R;
case 31: return ImGuiKey.S; case 20: return ImGuiKey.T;
case 22: return ImGuiKey.U; case 47: return ImGuiKey.V;
case 17: return ImGuiKey.W; case 45: return ImGuiKey.X;
case 21: return ImGuiKey.Y; case 44: return ImGuiKey.Z;
// 数字键
case 11: return ImGuiKey._0; case 2: return ImGuiKey._1;
case 3: return ImGuiKey._2; case 4: return ImGuiKey._3;
case 5: return ImGuiKey._4; case 6: return ImGuiKey._5;
case 7: return ImGuiKey._6; case 8: return ImGuiKey._7;
case 9: return ImGuiKey._8; case 10: return ImGuiKey._9;
// 功能键
case 59: return ImGuiKey.F1; case 60: return ImGuiKey.F2;
case 61: return ImGuiKey.F3; case 62: return ImGuiKey.F4;
case 63: return ImGuiKey.F5; case 64: return ImGuiKey.F6;
case 65: return ImGuiKey.F7; case 66: return ImGuiKey.F8;
case 67: return ImGuiKey.F9; case 68: return ImGuiKey.F10;
case 87: return ImGuiKey.F11; case 88: return ImGuiKey.F12;
// 方向键
case 203: return ImGuiKey.LeftArrow;
case 205: return ImGuiKey.RightArrow;
case 200: return ImGuiKey.UpArrow;
case 208: return ImGuiKey.DownArrow;
// 特殊键
case 15: return ImGuiKey.Tab;
case 28: return ImGuiKey.Enter;
case 1: return ImGuiKey.Escape;
case 14: return ImGuiKey.Backspace;
case 211: return ImGuiKey.Delete;
case 210: return ImGuiKey.Insert;
case 199: return ImGuiKey.Home;
case 207: return ImGuiKey.End;
case 201: return ImGuiKey.PageUp;
case 209: return ImGuiKey.PageDown;
case 57: return ImGuiKey.Space;
case 183: return ImGuiKey.PrintScreen;
case 197: return ImGuiKey.Pause;
case 58: return ImGuiKey.CapsLock;
case 69: return ImGuiKey.NumLock;
case 70: return ImGuiKey.ScrollLock;
// 修饰键
case 42: return ImGuiKey.LeftShift;
case 54: return ImGuiKey.RightShift;
case 29: return ImGuiKey.LeftCtrl;
case 157: return ImGuiKey.RightCtrl;
case 56: return ImGuiKey.LeftAlt;
case 184: return ImGuiKey.RightAlt;
case 219: return ImGuiKey.LeftSuper;
case 220: return ImGuiKey.RightSuper;
case 221: return ImGuiKey.Menu;
// 符号键
case 12: return ImGuiKey.Minus;
case 13: return ImGuiKey.Equal;
case 26: return ImGuiKey.LeftBracket;
case 27: return ImGuiKey.RightBracket;
case 43: return ImGuiKey.Backslash;
case 39: return ImGuiKey.Semicolon;
case 40: return ImGuiKey.Apostrophe;
case 51: return ImGuiKey.Comma;
case 52: return ImGuiKey.Period;
case 53: return ImGuiKey.Slash;
case 41: return ImGuiKey.GraveAccent;
// 小键盘
case 156: return ImGuiKey.KeypadEnter;
case 82: return ImGuiKey.Keypad0;
case 79: return ImGuiKey.Keypad1;
case 80: return ImGuiKey.Keypad2;
case 81: return ImGuiKey.Keypad3;
case 75: return ImGuiKey.Keypad4;
case 76: return ImGuiKey.Keypad5;
case 77: return ImGuiKey.Keypad6;
case 71: return ImGuiKey.Keypad7;
case 72: return ImGuiKey.Keypad8;
case 73: return ImGuiKey.Keypad9;
case 181: return ImGuiKey.KeypadDivide;
case 55: return ImGuiKey.KeypadMultiply;
case 74: return ImGuiKey.KeypadSubtract;
case 78: return ImGuiKey.KeypadAdd;
case 83: return ImGuiKey.KeypadDecimal;
default: return -1;
}
}
/**
* 判断GLSL版本
*/
protected String decideGlslVersion() {
Platform.Os os = JmeSystem.getPlatform().getOs(); //
if (os == Platform.Os.Windows) { //
return "#version 150"; //
} else if (isOpenGLVersionSupported(3, 3)) { //
return "#version 330"; //
} else {
return "#version 130"; //
}
}
/**
* 检查是否支持特定的OpenGL版本
*/
public boolean isOpenGLVersionSupported(int major, int minor) {
try {
String version = getOpenGLVersion(); //
if (version == null || version.equals("Unknown")) { //
return false;
}
String[] parts = version.split("\\s+")[0].split("\\."); //
if (parts.length >= 2) { //
int actualMajor = Integer.parseInt(parts[0]); //
int actualMinor = Integer.parseInt(parts[1]); //
if (actualMajor > major) { //
return true;
}
if (actualMajor == major && actualMinor >= minor) { //
return true;
}
}
return false; //
} catch (Exception e) {
return false;
}
}
public String getOpenGLVersion() {
try {
String version = glGetString(GL_VERSION);
return version != null ? version : "Unknown";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
public boolean isInitialized() {
return initialized;
}
}
Recently, I was playing with Vulkan and ignored the test for ImGui. When I saw the content in the post, I realized that I seemed to be missing the controller part
capdevon provided a comprehensive solution in the post.
Discussion in the ATmosphere